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
}
}
Related
My objective is simple: pre-validate a new password client-side (Javascript) as an initial check the password matches the domain/ou password policy, to avoid wasting server resources rejecting bad passwords and give faster responses to users.
The question: How can I get user password policy from Active Directory?
I especially need to know the password "format", password length, capital and special characters requirements, etc. The final validation will, of course, be Active Directory itself. But first I want to use Javascript as a performance optimization, and I'm pretty sure I can manage the Javascript if I can just retrieve the password format requirements for a specific user/OU on the C#/ASP.Net end.
Currently, I'm stuck trying to find WHAT the current password policy is for the user. Yes, user Alice might use the password domain policy, but Bob could have a different password policy in his OU.
This website will be installed in an institution with thousands of users; we want to minimize the back and forth validation against Active Directory. Additionally, having this in Javascript can eventually help in compliance with NIST Special Publication 800-63, which among other things asks for prompt feedback to users on relative password strength. For now, I must be able to make the code work on Windows 2008, 2008 R2 and 2012.
I'm currently able to change the password in C#, and I can get the error, but it's it's all or nothing, and not helpful for client-side validation.
public static PasswordChangeResultsDTO ChangeUserPassword(PasswordChangeRequestDTO request)
{
try
{
bool isPasswordChanged = false;
SearchResult result = LdapHelper.GetUser(request.Username, request.OldPassword);
if (result != null)
{
using (DirectoryEntry userEntry = result.GetDirectoryEntry())
{
userEntry.Invoke("ChangePassword", new object[] {request.OldPassword, request.NewPassword});
userEntry.CommitChanges();
isPasswordChanged = true;
}
}
return new PasswordChangeResultsDTO {PasswordChanged = isPasswordChanged};
}
catch (COMException comException)
{
LoggingHelper.Instance.WriteException(comException);
string message = comException.ErrorCode == -2147022651
? "The password does not meet the password policy requirements"
: comException.Message;
return new PasswordChangeResultsDTO {PasswordChanged = false, Message = message};
}
catch (TargetInvocationException targetInvocationException)
{
LoggingHelper.Instance.WriteException(targetInvocationException);
string message;
if (targetInvocationException.InnerException != null)
{
var comException = targetInvocationException.InnerException as COMException;
if (comException != null)
{
message = comException.ErrorCode == -2147022651
? "The password does not meet the password policy requirements"
: comException.Message;
}
else
{
message = targetInvocationException.InnerException.Message;
}
}
else
{
message = targetInvocationException.Message;
}
return new PasswordChangeResultsDTO {PasswordChanged = false, Message = message};
}
catch (Exception ex)
{
string msgError = (null != ex.InnerException) ? ex.InnerException.Message : ex.Message;
string msgSource = (null != ex.InnerException) ? ex.InnerException.Source : ex.Source;
string msgStackTrace = (null != ex.InnerException) ? ex.InnerException.StackTrace : ex.StackTrace;
string msgOutput = String.Format(CultureInfo.InvariantCulture,
"Exception in {3} MSG[{0}] SOURCE[{1}] STACK[{2}]",
msgError, msgSource, msgStackTrace, MethodBase.GetCurrentMethod().Name);
LoggingHelper.Instance.Fatal(msgOutput);
throw;
}
}
Finding out this information at the domain level is easy. Figuring out if any Group Policies have overridden the default is hard.
At the domain level, there are attributes at the domain itself that govern the default password policy for the domain. You can bind to the domain itself (i.e. LDAP://domain.com) and read these attributes:
minPwdLength: The minimum character length
pwdHistoryLength: The number of old passwords that can't be reused.
pwdProperties: This is a bit flag that could mean various things, which you can read about under the "PasswordProperties" section here. It's likely to be set to 1 (DOMAIN_PASSWORD_COMPLEX), which means a password must include at least two of either uppercase, lowercase and numbers.
If you want to go through the effort to read group policies that would apply to the user's OU, there doesn't seem to be any .NET libraries to do that. You have to resort to using unmanaged code. There is an example here that uses the IGPMDomain interface from C#, but you will have to adapt it to find the GPO for the right OU.
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
First off, does the AWS C# SDK self check itself? Right now, I have code that does stuff like
md5 checking
List<string> msgs = new List<string>();
ReceiveMessageResponse response = this.getMessageRoutine(num);
foreach (Message m in response.ReceiveMessageResult.Message) {
if (m.MD5OfBody.ToUpper() != Global.StringFunctions.CalculateMD5Hash(m.Body)) {
throw new Exception("TODO IMPLEMENT: RECEIVED MESSAGE IS CORRUPT");
}
msgs.Add(m.Body);
}
return msgs;
verifying that the user defined IDs sent out came back in the response, and that the md5 the server generated of the message matches the message sent
foreach (SendMessageBatchResultEntry e in response.SendMessageBatchResult.SendMessageBatchResultEntry) {
foreach (SendMessageBatchRequestEntry r in entry) {
if (r.Id == e.Id) {
if (Global.StringFunctions.CalculateMD5Hash(r.MessageBody) != e.MD5OfMessageBody.ToUpper()) {
throw new Exception("TODO IMPLEMENT THIS: MD5 MISMATCH BETWEEN AWS:LOCAL (" + e.MD5OfMessageBody.ToUpper() + " : " + Global.StringFunctions.CalculateMD5Hash(r.MessageBody)+")");
}
entry.Remove(r);
break;
}
throw new Exception("TODO IMPLEMENT THIS: INVALID ID IN RESPONSE");
}
}
if (entry.Count != 0) {
throw new Exception("TODO IMPLEMENT: NOT ALL MESSAGES SENT HAD A CORRESPONDING CALLBACK");
}
Is there a better implementation of something like this already created, something like
request.checkResponse(response) ?
The most recent version of the AWS SDK for .NET (1.5.23) includes the MD5 hash check as part of sending a single message, sending a batch of messages, and receiving messages. If that does not work for you, we would love to hear why and how we could make it better.
Thanks!
I have read on SO about validating emails with MailAddress and have produced the folowing code:
try
{
var address = new MailAddress(value.Email);
}
catch (FormatException)
{
emailError = "Invalid Email";
}
To my great surprise the string ndskfndlfk#sdflsdf validates as good email.
Any idea why is it so?
If you really need to filter those email addresses, you could use the little utility found here, which uses Regex to validate email addresses. Note that this is the .Net 4.0 version - it's a little different for 4.5.
using System;
using System.Globalization;
using System.Text.RegularExpressions;
public class RegexUtilities
{
bool invalid = false;
public bool IsValidEmail(string strIn)
{
invalid = false;
if (String.IsNullOrEmpty(strIn))
return false;
// Use IdnMapping class to convert Unicode domain names.
strIn = Regex.Replace(strIn, #"(#)(.+)$", this.DomainMapper);
if (invalid)
return false;
// Return true if strIn is in valid e-mail format.
return Regex.IsMatch(strIn,
#"^(?("")(""[^""]+?""#)|(([0-9a-z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-z])#))" +
#"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-z][-\w]*[0-9a-z]*\.)+[a-z0-9]{2,17}))$",
RegexOptions.IgnoreCase);
}
private string DomainMapper(Match match)
{
// IdnMapping class with default property values.
IdnMapping idn = new IdnMapping();
string domainName = match.Groups[2].Value;
try {
domainName = idn.GetAscii(domainName);
}
catch (ArgumentException) {
invalid = true;
}
return match.Groups[1].Value + domainName;
}
}
Otherwise, if it's for user registration, I usually just depend on email verification - i.e. the system sends the user an email with a link, and clicking the link therefore validates that the email address is real.
It is possible for a valid email address not to be a real one. In other words, it may even pass a perfect validation system, but that's no guarantee that it exists.
That might not be valid WWW domain name but on a private network I think it is a valid domain name. If you want to get more selective then Regex.
I try and parse legit email (eliminate phony used by spam) using Regex and it is messy.
If you have access to the actual email header then it does not really get easier but it get more accurate.
I want to automate Outlook so that I can download the "pieces parts" of email messages so that I can tie related messages together. I understand that email usually has a "MessageID" to serve this purpose, so that emails can be viewed in context, as "threads" in a newsreader are tied together.
Does Outlook have the notion of "Message IDs" in emails sent with it? I see that the elements that can be extracted (using automation) are Subject, SenderEmail, CreationTime, Body, SenderName, and HTMLBody. Is a "message id" or equivalent available somewhere, too?
Outlook tracks related messages by using Conversations.
In Outlook 2003, there is ConversationTopic (MAPI: PR_CONVERSATION_TOPIC) & ConversationIndex
(MAPI: PR_CONVERSATION_INDEX). ConversationTopic is typically the message subject (minus prefixes - RE:/FW:, etc.), while ConversationIndex represents the sequential ordering of the ConversationTopic (essentially GUID + timestamp). See Working with Conversations on MSDN. ConversationIndex is explicitly defined on MSDN here.
In Outlook 2010, they added ConversationID (MAPI: PR_CONVERSATION_ID) which is derived from the ConversationTopic. ConversationID can be generated from the ConversationTopic as discussed here.
For more detailed info about the MSG protocol specs regarding Conversations see [MS-OXOMSG]: E-Mail Object Protocol Specification, section 2.2.1.2 and 2.2.1.3.
Small addition to previous great answer. In case if anyone else will also need C# implementation of algorithm used to retrieve ConversationID from ConversationIndex/ConversationTopic:
private const int c_ulConvIndexIDOffset = 6;
private const int c_ulConvIndexIDLength = 16;
private string GetConversationId()
{
var convTracking = GetMapiPropertyBool(PR_CONVERSATION_INDEX_TRACKING);
var convIndex = GetMapiPropertyBytes(PR_CONVERSATION_INDEX);
byte[] idBytes;
if (convTracking
&& convIndex != null
&& convIndex.Length > 0)
{
// get Id from Conversation index
idBytes = new byte[c_ulConvIndexIDLength];
Array.Copy(convIndex, c_ulConvIndexIDOffset, idBytes, 0, c_ulConvIndexIDLength);
}
else
{
// get Id from Conversation topic
var topic = GetMapiPropertyString(PR_CONVERSATION_TOPIC);
if (string.IsNullOrEmpty(topic))
{
return string.Empty;
}
if (topic.Length >= 265)
{
topic = topic.Substring(0, 256);
}
topic = topic.ToUpper();
using (var md5 = new System.Security.Cryptography.MD5CryptoServiceProvider())
{
idBytes = md5.ComputeHash(Encoding.Unicode.GetBytes(topic));
}
}
return BitConverter.ToString(idBytes).Replace("-", string.Empty);
}
GetMapiProperty...() is a helper functions which just retrieve required MAPI property and cast result to appropriate managed type