Why do I not get exceptions when MailKit SmtpClient.Send() fails? - c#

I want to get- and handle exceptions when the MailKit SmtpClient.Send() fails to send to an invalid email. But it doesn't throw any exceptions.
I have tried sending to an invalid email on purpose to see if I can catch any exceptions. Does the MailKit SmtpClient simply not throw any exceptions when failing to send? In case you wonder why I need this, is that I want to return an error message to a client application that some emails were invalid.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using MyProject.Backend.Models;
using MailKit.Net.Smtp;
using MimeKit;
using MimeKit.Text;
namespace MyProject.Backend.Helpers
{
public class SMTPHelper
{
SMTPSettings smtpSettings;
public SMTPHelper(SMTPSettings smtpSettings)
{
this.smtpSettings = smtpSettings;
}
public List<string> Send(List<PreparedEmailModel> preparedEmails)
{
using (var emailClient = new SmtpClient())
{
var failedEmails = new List<string>();
//The last parameter here is to use SSL (Which you should!)
emailClient.Connect(smtpSettings.Host, 587);
//Remove any OAuth functionality as we won't be using it.
emailClient.AuthenticationMechanisms.Remove("XOAUTH2");
emailClient.Authenticate(smtpSettings.CredentialsUser, smtpSettings.CredentialsPassword);
foreach (var preparedEmail in preparedEmails)
{
var message = new MimeMessage();
message.From.Add(new MailboxAddress(preparedEmail.FromEmail));
message.To.Add(new MailboxAddress(preparedEmail.ToEmail));
message.Subject = preparedEmail.Subject;
//We will say we are sending HTML. But there are options for plaintext etc.
message.Body = new TextPart(TextFormat.Html)
{
Text = preparedEmail.Body
};
try
{
emailClient.Send(message);
}
catch (Exception e)
{
failedEmails.Add(preparedEmail.ToEmail);
}
}
emailClient.Disconnect(true);
return failedEmails;
}
}
}
}
Expected: MailKit SmtpClient.Send() should generate an exception when sending to an invalid email address.
Result: No exceptions are thrown even when trying with an invalid made-up email address.

Let's say you go to the post office. You put an envelope in the mailbox, addressed to a non-existent address.
Does the mailbox shoot it back at you, and say it doesn't exist? No.
The post office sends it somewhere, they try and deliver it, they fail and eventually it comes back to you.
Email works basically the same way. You don't know at send time if the target email address is legitimate or not.
As such, your code is working as expected.
If you care deeply about this stuff, you need to do bounce processing - but the bounce might arrive days later, or never. This is not a small job (I work at a company that specialises in that kind of thing).

There is difference between INVALID address and NOT EXISTING (wrong) address.
If you will try to send email to the address that doesn't meet email address format
(XXX#AnyDomain for example XXX#gmail.com) MailKit will throw exception.
If you send to the proper format but the address is wrong MailKit doesn't throw any exception

Related

Best way to validate email address format in C#

Hi I am working on a bulk email sending feature. Below is my loop that validates email and sends them to each recipient:
foreach (var userID in recipientUserIds)
{
var userInfo = //getting from database using userID.
try
{
to = new MailAddress(userInfo.Email1, userInfo.FirstName + " " + userInfo.LastName);
}
catch (System.FormatException fe)
{
continue;
}
using (MailMessage message = new MailMessage(from, to))
{
//populate message and send email.
}
}
Since the recipientUserIds is always more than 2000, using try-catch seems to be very expensive for each user in this case just to validate the email address format. I was wondering to use regex, but not sure if that will help with performance.
So my question is if there is any better or performance optimized way to do the same validation.
Validating email addresses is a complicated task, and writing code to do all the validation up front would be pretty tricky. If you check the Remarks section of the MailAddress class documentation, you'll see that there are lots of strings that are considered a valid email address (including comments, bracketed domain names, and embedded quotes).
And since the source code is available, check out the ParseAddress method here, and you'll get an idea of the code you'd have to write to validate an email address yourself. It's a shame there's no public TryParse method we could use to avoid the exception being thrown.
So it's probably best to just do some simple validation first - ensure that it contains the minimum requirements for an email address (which literally appears to be user#domain, where domain does not have to contain a '.' character), and then let the exception handling take care of the rest:
foreach (var userID in recipientUserIds)
{
var userInfo = GetUserInfo(userID);
// Basic validation on length
var email = userInfo?.Email1?.Trim();
if (string.IsNullOrEmpty(email) || email.Length < 3) continue;
// Basic validation on '#' character position
var atIndex = email.IndexOf('#');
if (atIndex < 1 || atIndex == email.Length - 1) continue;
// Let try/catch handle the rest, because email addresses are complicated
MailAddress to;
try
{
to = new MailAddress(email, $"{userInfo.FirstName} {userInfo.LastName}");
}
catch (FormatException)
{
continue;
}
using (MailMessage message = new MailMessage(from, to))
{
// populate message and send email here
}
}

Sending Email to SpecifiedPickupDirectory with MailKit

I was using SmtpClient till now with ASP.NET MVC 5. For testing email send functionality on local system, I was using client.DeliveryMethod = SmtpDeliveryMethod.SpecifiedPickupDirectory;
Now, I want to do the same things in ASP.NET Core which does not have SmtpClient class implemented till now. All search for this ended up on MailKit. I have used their send mail code which is working fine with gmail.
I do not want to send testing emails each time and there may be a lot of scenarios in my project where I need to send email. How can I use the local email sending functionality with MailKit. Any links or little source code will help. Thanks
I'm not sure on the finer details of how SmtpDeliveryMethod.SpecifiedPickupDirectory works and what it does exactly, but I suspect it might just save the message in a directory where the local Exchange server periodically checks for mail to send out.
Assuming that's the case, you could do something like this:
public static void SaveToPickupDirectory (MimeMessage message, string pickupDirectory)
{
do {
// Generate a random file name to save the message to.
var path = Path.Combine (pickupDirectory, Guid.NewGuid ().ToString () + ".eml");
Stream stream;
try {
// Attempt to create the new file.
stream = File.Open (path, FileMode.CreateNew);
} catch (IOException) {
// If the file already exists, try again with a new Guid.
if (File.Exists (path))
continue;
// Otherwise, fail immediately since it probably means that there is
// no graceful way to recover from this error.
throw;
}
try {
using (stream) {
// IIS pickup directories expect the message to be "byte-stuffed"
// which means that lines beginning with "." need to be escaped
// by adding an extra "." to the beginning of the line.
//
// Use an SmtpDataFilter "byte-stuff" the message as it is written
// to the file stream. This is the same process that an SmtpClient
// would use when sending the message in a `DATA` command.
using (var filtered = new FilteredStream (stream)) {
filtered.Add (new SmtpDataFilter ());
// Make sure to write the message in DOS (<CR><LF>) format.
var options = FormatOptions.Default.Clone ();
options.NewLineFormat = NewLineFormat.Dos;
message.WriteTo (options, filtered);
filtered.Flush ();
return;
}
}
} catch {
// An exception here probably means that the disk is full.
//
// Delete the file that was created above so that incomplete files are not
// left behind for IIS to send accidentally.
File.Delete (path);
throw;
}
} while (true);
}
The above code snippet uses Guid.NewGuid () as a way of generating a temporary filename, but you can use whatever method you want (e.g. you could also opt to use message.MessageId + ".eml").
Based on Microsoft's referencesource, when SpecifiedPickupDirectory is used, they actually also use Guid.NewGuid ().ToString () + ".eml", so that's probably the way to go.

How to email large files using c# windows application

I'm developing an windows application in which i need to send some files as attachment through email.
Code
public string SendMail(string mFrom,
string mPass,
string mTo,
string mSub,
string mMsg,
string mFile,
bool isDel)
{
string sql = "";
try
{
System.Net.Mail.MailAddress mailfrom = new System.Net.Mail.MailAddress(mFrom);
System.Net.Mail.MailAddress mailto = new System.Net.Mail.MailAddress(mTo);
System.Net.Mail.MailMessage newmsg = new System.Net.Mail.MailMessage(mailfrom, mailto);
newmsg.IsBodyHtml = false;
if (mFile.Length > 2
&& File.Exists(mFile))
{
System.Net.Mail.Attachment att = new System.Net.Mail.Attachment(mFile);
newmsg.Attachments.Add(att);
}
newmsg.Subject = mSub;
newmsg.Body = mMsg;
System.Net.Mail.SmtpClient smtp = new System.Net.Mail.SmtpClient("smtp.gmail.com", 587);
smtp.UseDefaultCredentials = false;
smtp.Credentials = new System.Net.NetworkCredential(mFrom, mPass);
smtp.EnableSsl = true;
smtp.Send(newmsg);
newmsg.Dispose();
GC.Collect();
sql = "OK";
if (isDel
&& File.Exists(mFile))
{
File.Delete(mFile);
}
}
catch (Exception ex)
{
sql = ex.Message;
}
return sql;
}
This code works fine for small files.But i need to send large files up to 1-2 GB.
For that what to do.
You cannot use e-mail to get these files across and this has nothing to do with your code.
I don't think there is ANY provider out there who will support sending files of that size let alone receiving them. Even G-Mail has a limit of 25 Mb which is quite large already.
E-Mail is not the proper channel to do this.
So the problem will not be in your code, the provider will limit the size of the attachment and just refuse them when you present them with a larger file. You will get an e-mail back at your FROM address stating that the file is too large and your e-mail did not get across.
For doing this in the simplest form probably look at FTP.
I do agree with Gerald Versluis in that email is not the proper channel for this. Even if you are using your own email server that is configurable there is probably some internal limit that prevents it from sending such big files.
I’d go with FTP for this but if you really want to continue with email I’d suggest you check following first.
Is there connection timeout property on the server? If yes then try to increase it to 3 hours or something like that.
Is there enough space on the mail server?
Is there some documentation for your email server? Are there any additional details regarding attachment size ?

Notify C# Client, when SMTP Server receive a new Email

I want to get all emails in my ASP.NET application that have a certain CC-recipient. To use this for future emails I didn't want to polling all the time to get them. But I can't find a way, how I can use push to get the emails instantly. Are their any frameworks in C# to help me for this?
I want to connect with my application to a mail server and register a method 'X'. Always when a new message arrived to the mail server, my application have to be notified and my application should execute the method 'X'.
I hope that this is possible with code like this:
void Application_Start()
{
...
ConnectWithTheSmtpServer();
RegisterMethodForNotification(DoSomethink);
...
}
void DoSomethink(Mail newMail)
{
// Do Somethink with the mail
}
EDIT:
I did it with the MailSystem.Net. It works very fine and is very easy to implement.
Sample Code:
void Application_Start()
{
var worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(StartIdleProcess);
if (worker.IsBusy)
worker.CancelAsync();
worker.RunWorkerAsync();
}
private void StartIdleProcess(object sender, DoWorkEventArgs e)
{
if (_imap != null && _imap.IsConnected)
{
_imap.StopIdle();
_imap.Disconnect();
}
_imap = new Imap4Client();
_imap.ConnectSsl(server-name, 993);
_imap.Login(username, passwort);
var inbox = _imap.SelectMailbox("INBOX");
_imap.NewMessageReceived += new NewMessageReceivedEventHandler(NewMessageReceived);
inbox.Subscribe();
_imap.StartIdle();
}
public static void NewMessageReceived(object source, NewMessageReceivedEventArgs e)
{
// Do something with the source...
}
You are approaching this from the wrong angle.
SMTP does not support receiving mail (never mind PUSH mail). POP3 is what you can use for retrieving mail, but it does not have support for PUSH either (so you would have to pull for mail).
The IMAP4 IDLE extension is what most refer to as PUSH mail - so you will need to find a library for C# that supports IMAP4 IDLE. I found some information that will get you going in the right direction (no reason to duplicate it here):
Using C# .Net Libraries to Check for IMAP Messages
Accessing IMAP in C#
Keep in mind when choosing a solution that it needs to support IDLE.
I really like the look of MailSystem.Net as it fulfills your requirements.
Remember that your mail server also needs to have IMAP4 and IMAP4 IDLE enabled. Some mail servers don't support it, so you might be clean out of luck (and will have to use POP3 pulling).
You could send a copy of your emails(i.e. using /etc/aliases file in PostFix) to a MAIL SERVER YOU CAN HANDLE. Once there, you can implement a MAIL PROCESSOR that do whatever you want anytime a mail that MEET CERTAIN CONDITIONS arrives.
Hope that helps,
You can try this:
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using EAGetMail; //add EAGetMail namespace
namespace receiveemail
{
class Program
{
static void Main(string[] args)
{
// Create a folder named "inbox" under current directory
// to save the email retrie enter code here ved.
string curpath = Directory.GetCurrentDirectory();
string mailbox = String.Format("{0}\\inbox", curpath);
// If the folder is not existed, create it.
if (!Directory.Exists(mailbox))
{
Directory.CreateDirectory(mailbox);
}
// Gmail IMAP4 server is "imap.gmail.com"
MailServer oServer = new MailServer("imap.gmail.com",
"gmailid#gmail.com", "yourpassword", ServerProtocol.Imap4 );
MailClient oClient = new MailClient("TryIt");
// Set SSL connection,
oServer.SSLConnection = true;
// Set 993 IMAP4 port
oServer.Port = 993;
try
{
oClient.Connect(oServer);
MailInfo[] infos = oClient.GetMailInfos();
for (int i = 0; i < infos.Length; i++)
{
MailInfo info = infos[i];
Console.WriteLine("Index: {0}; Size: {1}; UIDL: {2}",
info.Index, info.Size, info.UIDL);
// Download email from GMail IMAP4 server
Mail oMail = oClient.GetMail(info);
Console.WriteLine("From: {0}", oMail.From.ToString());
Console.WriteLine("Subject: {0}\r\n", oMail.Subject);
// Generate an email file name based on date time.
System.DateTime d = System.DateTime.Now;
System.Globalization.CultureInfo cur = new
System.Globalization.CultureInfo("en-US");
string sdate = d.ToString("yyyyMMddHHmmss", cur);
string fileName = String.Format("{0}\\{1}{2}{3}.eml",
mailbox, sdate, d.Millisecond.ToString("d3"), i);
// Save email to local disk
oMail.SaveAs(fileName, true);
// Mark email as deleted in GMail account.
oClient.Delete(info);
}
// Quit and purge emails marked as deleted from Gmail IMAP4 server.
oClient.Quit();
}
catch (Exception ep)
{
Console.WriteLine(ep.Message);
}
}
}
}

Check for unread emails

I'm looking for a way to check the number of unread emails on an email account.
Any tips?
EDIT: As described in the tags, for C#. As I learned IMAP is the way to go and I confirmed all email accounts I'm going to use have IMAP activated :)
POP
You can use OpenPOP.net to read emails using POP protocol. The problem with POP is that it does not hold details whether it was unread or not. So I think this will not be of much use to you. You have have your own way of downloading and tagging emails as read or unread.
IMAP
This question in SO has some links for examples using IMAP. IMAP has details about mail status(read/unread).
Please explain more about your requirement.
If what you want to do is get the number of unread messages in an IMAP folder, you can use MailKit to do this:
using MailKit;
using MailKit.Search;
using MailKit.Net.Imap;
...
using (var client = new ImapClient ()) {
// Note: depending on your server, you might need to connect
// on port 993 using SecureSocketOptions.SslOnConnect
client.Connect ("imap.server.com", 143, SecureSocketOptions.StartTls);
// Note: use your real username/password here...
client.Authenticate ("username", "password");
// open the Inbox folder...
client.Inbox.Open (FolderAccess.ReadOnly);
// search the folder for new messages (aka recently
// delivered messages that have not been read yet)
var uids = client.Inbox.Search (SearchQuery.New);
Console.WriteLine ("You have {0} new message(s).", uids.Count);
// ...but maybe you mean unread messages? if so, use this query
uids = client.Inbox.Search (SearchQuery.NotSeen);
Console.WriteLine ("You have {0} unread message(s).", uids.Count);
client.Disconnect (true);
}
Here is the sample of code with LumiSoft IMAP library:
using LumiSoft.Net.IMAP;
using LumiSoft.Net.IMAP.Client;
using LumiSoft.Net;
...
using (IMAP_Client client = new IMAP_Client())
{
client.Connect("imap.gmail.com", 993, true);
client.Login("your.username#gmail.com", "your_cool_password");
client.SelectFolder("INBOX");
IMAP_SequenceSet sequence = new IMAP_SequenceSet();
//sequence.Parse("*:1"); // from first to last
IMAP_Client_FetchHandler fetchHandler = new IMAP_Client_FetchHandler();
fetchHandler.NextMessage += new EventHandler(delegate(object s, EventArgs e)
{
Console.WriteLine("next message");
});
fetchHandler.Envelope += new EventHandler<EventArgs<IMAP_Envelope>>(delegate(object s, EventArgs<IMAP_Envelope> e){
IMAP_Envelope envelope = e.Value;
if (envelope.From != null && !String.IsNullOrWhiteSpace(envelope.Subject))
{
Console.WriteLine(envelope.Subject);
}
});
// the best way to find unread emails is to perform server search
int[] unseen_ids = client.Search(false, "UTF-8", "unseen");
Console.WriteLine("unseen count: " + unseen_ids.Count().ToString());
// now we need to initiate our sequence of messages to be fetched
sequence.Parse(string.Join(",", unseen_ids));
// fetch messages now
client.Fetch(false, sequence, new IMAP_Fetch_DataItem[] { new IMAP_Fetch_DataItem_Envelope() }, fetchHandler);
// uncomment this line to mark messages as read
// client.StoreMessageFlags(false, sequence, IMAP_Flags_SetType.Add, IMAP_MessageFlags.Seen);
}
Bit complicated, but works fine. Limisoft library is not perfect, so be sure you test it well.

Categories

Resources