The original question seems to be answered by @mason regarding the misuse of async.
So now to answer your new question.
The MessageSent event is just like any other event in .NET and can be listened to in the following way:
client.MessageSent += OnMessageSent;
Where the OnMessageSent
method is something like this:
void OnMessageSent (object sender, MessageSentEventArgs e)
{
Console.WriteLine ("The message was sent!");
}
However, the reason you are wanting to listen for this event seems to be a misunderstanding of what information it really provides you with.
While, yes, the MessageSentEventArgs.Response property contains the actual response sent by the server, it is unlikely to tell you whether or not the recipient email address(es) actually exist or not.
If you are sending a message to a non-existent email address and SmtpClient.Send()
or SendAsync()
does not throw an exception, then it means that the SMTP server likely is not verifying whether the email addresses exist when it receives the RCPT TO
commands sent by MailKit and will happily accept the message submission w/o error which means no exception will be thrown by MailKit. A lot of SMTP servers do this for 2 reasons:
- Protecting the anonymity of their users (so spammers can't use brute force techniques to figure out what their user's account names are - same reason man disable the
VRFY
and EXPN
commands).
- Lazy lookups of email addresses - i.e. the SMTP server doesn't actually look up the existence of the email address until it proceeds to forward the message to the appropriate domain.
For example, if you connect to smtp.gmail.com to send a message to a user on another domain, then there's no way for smtp.gmail.com to know that user@another-domain.com doesn't exist until it actually attempts to forward the message on to e.g. smtp.another-domain.com.
If you actually want to get feedback as to whether an email address actually exists or not, the process will involve a bit more effort on your part and some luck.
The Luck.
First, you'll need to hope and pray that your SMTP server supports the DSN (Delivery Status Notification) extension.
To check if your server supports this, you can check SmtpClient.Capabilities:
if (client.Capabilities.HasFlag (SmtpCapability.Dsn)) {
...
}
The Effort.
Assuming your server supports the DSN extension, next you'll need to subclass SmtpClient so that you can override some methods in order to provide MailKit's SmtpClient with some needed information/options.
These methods are:
- GetDeliveryStatusNotifications
- GetEnvelopeId
The documentation for both methods already provides the following code-snippet, but I'll paste it here for posterity:
public class DSNSmtpClient : SmtpClient
{
public DSNSmtpClient ()
{
}
/// <summary>
/// Get the envelope identifier to be used with delivery status notifications.
/// </summary>
/// <remarks>
/// <para>The envelope identifier, if non-empty, is useful in determining which message
/// a delivery status notification was issued for.</para>
/// <para>The envelope identifier should be unique and may be up to 100 characters in
/// length, but must consist only of printable ASCII characters and no white space.</para>
/// <para>For more information, see rfc3461, section 4.4.</para>
/// </remarks>
/// <returns>The envelope identifier.</returns>
/// <param name="message">The message.</param>
protected override string GetEnvelopeId (MimeMessage message)
{
// Since you will want to be able to map whatever identifier you return here to the
// message, the obvious identifier to use is probably the Message-Id value.
return message.MessageId;
}
/// <summary>
/// Get the types of delivery status notification desired for the specified recipient mailbox.
/// </summary>
/// <remarks>
/// Gets the types of delivery status notification desired for the specified recipient mailbox.
/// </remarks>
/// <returns>The desired delivery status notification type.</returns>
/// <param name="message">The message being sent.</param>
/// <param name="mailbox">The mailbox.</param>
protected override DeliveryStatusNotification? GetDeliveryStatusNotifications (MimeMessage message, MailboxAddress mailbox)
{
// In this example, we only want to be notified of failures to deliver to a mailbox.
// If you also want to be notified of delays or successful deliveries, simply bitwise-or
// whatever combination of flags you want to be notified about.
return DeliveryStatusNotification.Failure;
}
}
Okay, now that you've done the above... this will request that the SMTP server sends you an email if/when the server fails to deliver the message to any of the recipients.
Now you get to handle receiving said emails...
When you get one of these messages, it will have a top-level Content-Type
of multipart/report; report-type="delivery-status"
which will be represented by a MultipartReport
The way to detect this is:
var report = message.Body as MultipartReport;
if (report != null && report.ReportType != null && report.ReportType.Equals ("delivery-status", StringComparison.OrdinalIgnoreCase)) {
...
}
Then what you will need to do is locate the MIME part(s) with a Content-Type
of message/delivery-status
that are children of the multipart/report
(each of which will be represented by MessageDeliveryStatus):
foreach (var mds in report.OfType<MessageDeliveryStatus> ()) {
...
}
Then you'll need to check the StatusGroups in order to extract the information you need. The StatusGroups
property is a HeaderListCollection
which is essentially a list of a list of key-value pairs.
To figure out what keys are available, you'll need to read over Section 2.2 and Section 2.3 of rfc3464.
At a minimum, you'll need to check the "Original-Envelope-Id"
in the first StatusGroup in order to figure out which message the report is for (this envelope id string will match the string you returned in GetEnvelopeId
).
var envelopeId = mds.StatusGroups[0]["Original-Envelope-Id"];
In each of the following StatusGroups, you'll want to get the value for the "Original-Recipient"
(if set, otherwise I guess you could check the "Final-Recipient"
). This will be of the form rfc822;user@domain.com
- so just split on the ';'
character and use the second string.
And finally you'll want to check the "Action"
value to figure out what the status of said recipient is. In your case, if the value is "failed"
, then it means that delivery failed.
for (int i = 1; i < mds.StatusGroups.Length; i++) {
var recipient = mds.StatusGroups[i]["Original-Recipient"];
var action = mds.StatusGroups[i]["Action"];
if (recipient == null)
recipient = mds.StatusGroups[i]["Final-Recipient"];
var values = recipient.Split (';');
var emailAddress = values[1];
...
}
If you put it all together, you get something like this:
public void ProcessDeliveryStatusNotification (MimeMessage message)
{
var report = message.Body as MultipartReport;
if (report == null || report.ReportType == null || !report.ReportType.Equals ("delivery-status", StringComparison.OrdinalIgnoreCase)) {
// this is not a delivery status notification message...
return;
}
// process the report
foreach (var mds in report.OfType<MessageDeliveryStatus> ()) {
// process the status groups - each status group represents a different recipient
// The first status group contains information about the message
var envelopeId = mds.StatusGroups[0]["Original-Envelope-Id"];
// all of the other status groups contain per-recipient information
for (int i = 1; i < mds.StatusGroups.Length; i++) {
var recipient = mds.StatusGroups[i]["Original-Recipient"];
var action = mds.StatusGroups[i]["Action"];
if (recipient == null)
recipient = mds.StatusGroups[i]["Final-Recipient"];
// the recipient string should be in the form: "rfc822;user@domain.com"
var index = recipient.IndexOf (';');
var address = recipient.Substring (index + 1);
switch (action) {
case "failed":
Console.WriteLine ("Delivery of message {0} failed for {1}", envelopeId, address);
break;
case "delayed":
Console.WriteLine ("Delivery of message {0} has been delayed for {1}", envelopeId, address);
break;
case "delivered":
Console.WriteLine ("Delivery of message {0} has been delivered to {1}", envelopeId, address);
break;
case "relayed":
Console.WriteLine ("Delivery of message {0} has been relayed for {1}", envelopeId, address);
break;
case "expanded":
Console.WriteLine ("Delivery of message {0} has been delivered to {1} and relayed to the the expanded recipients", envelopeId, address);
break;
}
}
}
}