BACKGROUND
I have written a little console application that monitors a RabbitMQ queue for emails. Whenever an email is pushed onto the queue, my application would pick up that email, process it and send it.
CODE
Below is the code for my email service, it is what actually sends out the email.
public class MailService : IMailService
{
private readonly SmtpClient _smtpClient = new SmtpClient();
public void SendEmail(string toEmail, string subject, string body, bool isHtml)
{
var emailMessage = BuildEmailMessage(toEmail.Trim(), subject.Trim(), body, isHtml);
_smtpClient.SendAsync(emailMessage, null);
}
#region Helpers
private static MailMessage BuildEmailMessage(string toEmail, string subject, string body, bool isHtml)
{
const string fromEmailAddress = "james.brown#world.com";
var emailMessage = new MailMessage(fromEmailAddress, toEmail, subject, body) { IsBodyHtml = isHtml };
return emailMessage;
}
#endregion
}
PROBLEM
I had 2 emails on the RabbitMQ queue, and when I fired up my console application consumer, it threw the following exception after sending the first email (I received that 1st email in my inbox).
An asynchronous call is already in progress. It must be completed or canceled before you can call this method.
I did some digging around, and saw this thread which explains why I got this. Apparently using SendAsync() isn't the way to go because:
After calling SendAsync, you must wait for the e-mail transmission to complete before attempting to send another e-mail message using Send or SendAsync.
So what is the recommend way of going about this? I can create a new instance of the SmtpClient class for each email, but is that really a good idea?
Send the email synchronously, there's no need to use the older async syntax of SendAsync. Also, ensure your SmtpClient only gets hit from one thread at a time by wrapping it in a using statement. There is a slight performance penalty, but you probably won't notice it unless you're sending a ton of emails. And if you are sending a ton, then overload your MailService.SendEmail method to accept an IEnumerable<EmailModel> and send them all at once using a single SmtpClient.
public void SendEmail(string toEmail, string subject, string body, bool isHtml)
{
var emailMessage = BuildEmailMessage(toEmail.Trim(), subject.Trim(), body, isHtml);
using(var client = new SmtpClient())
{
_smtpClient.Send(emailMessage);
}
}
//this would be the right way to do async
public async Task SendEmailAsync(string toEmail, string subject, string body, bool isHtml)
{
var emailMessage = BuildEmailMessage(toEmail.Trim(), subject.Trim(), body, isHtml);
using(var client = new SmtpClient())
{
_smtpClient.SendMailAsync(emailMessage);
}
}
//this would be the right way to do multiple emails
//you'd need to create an EmailModel class to contain all the details for each email (similar to MailMessage, but it would prevent your own code from taking a dependency on System.Net.Mail
public void SendEmail(IEnumerable<EmailModel> emailModels)
{
var mailMessages = emailModels.Select(em => ConvertEmailModelToMailMessage(em));
using(var client = new SmtpClient())
{
foreach(var mailMessage in mailMessages)
{
//you may want some error handling on the below line depending on whether you want all emails to attempt to send even if one encounters an error
_smtpClient.Send(mailMessage);
}
}
}
private MailMessage ConvertEmailModelToMailMessage(EmailModel emailModel)
{
//do conversion here
}
Related
The original post was removed and I thought I would revise my latest issue. I understand completely that it has something to do with my username and password but am not sure of what else I can do. I have rest passwords multiple times, deleted and reestablished the username/email address multiple times and even dumped the .Net SmtpClient for the MailKit approach which I am now getting this error.
I am wonder if it has anything to do with me going through Bluehost for my domain and office365 subscription. With that said, as I began developing this application, I have noticed through Telnet I am still unable to establish a connection. Does anybody have any advice on how to send an email with SMTP (or anyway) through office365/outlook?
Here is my code:
Controller:
[HttpPost]
public async Task<IActionResult> SendContactEmail(ContactCardModel contact)
{
string emailSubject = $"Inquiry from {contact.name} from {contact.organization}";
await _emailSender.SendEmailAsync(contact.name, contact.email, emailSubject, contact.message);
ViewBag.ConfirmMsg = "Sent Successful";
return View("Contact");
}
Email Service:
public class SendEmailService : ISendEmail
{
private string _host;
private string _from;
private string _pwd;
public SendEmailService(IConfiguration configuration)
{
//TODO: Collect SMTP Configuration Settings
var smtpSection = configuration.GetSection("SMTP");
_host = smtpSection.GetSection("Host").Value;
_from = smtpSection.GetSection("From").Value;
_pwd = smtpSection.GetSection("Pwd").Value;
}
public async Task SendEmailAsync(string fromName, string fromEmail, string subject, string message)
{
//TODO: Build MailMessage Object
MimeMessage mailMessage = new MimeMessage();
mailMessage.From.Add(new MailboxAddress(fromName, fromEmail));
mailMessage.To.Add(new MailboxAddress("App Admin", "tyler.crane#odin-development.com"));
mailMessage.Subject = subject;
BodyBuilder bodyBuilder = new BodyBuilder
{
HtmlBody = message
};
//TODO: Build SmtpClient Object and NetworkCredential Object
SmtpClient smtp = new SmtpClient();
smtp.ServerCertificateValidationCallback = (sender, certificate, certChainType, errors) => true;
smtp.AuthenticationMechanisms.Remove("XOAUTH2");
await smtp.ConnectAsync(_host, 587, SecureSocketOptions.StartTls).ConfigureAwait(false);
await smtp.AuthenticateAsync(new NetworkCredential(_from, _pwd)).ConfigureAwait(false);
await smtp.SendAsync(mailMessage).ConfigureAwait(false);
}
}
Interface:
public interface ISendEmail
{
Task SendEmailAsync(
string fromName,
string fromEmail,
string subject,
string message
);
}
Greatly appreciate anybody willing to help!
I finally figured out my own issue and it wasn't even in the slightest bit that difficult. More importantly, the message itself was very misleading and I am here to shed some light for those who are encountering the same issue.
SmtpClient smtp = new SmtpClient();
smtp.ServerCertificateValidationCallback = (s, c, h, e) => true;
// The above Certificate Validation Callback has to be exactly as I show it.
// I, for some reason, had invalid options applied and can assure anyone who
// has followed any tutorial whatsoever, what they have inputted is wrong or for dummy
// testing purposes. Once you have this established, host has to be exactly as
// follows: smpt.office365.com and port 587 ONLY(25 is not longer supported).
smtp.AuthenticationMechanisms.Remove("XOAUTH2");
await smtp.ConnectAsync(_host, 587, SecureSocketOptions.StartTls).ConfigureAwait(false);
await smtp.AuthenticateAsync(new NetworkCredential(_from, _pwd)).ConfigureAwait(false);
await smtp.SendAsync(mailMessage).ConfigureAwait(false);
In no way shape or form did my error apply to the actual account itself. Although this may not directly apply to my issue where the tenant username/pass were not the issue, it may still be an issue for anyone. However, I do highly suggest you consider exactly what my code reflects above with the host and port suggestions I have made.
Thank you all who attempted to try and solve this and if anyone has any additional questions, I would be more than happy to answer them. Thanks!
I'm trying get unread mails from Gmail by IMAP protocol. I followed this tuto : https://briancaos.wordpress.com/2012/04/24/reading-mails-using-imap-and-mailsystem-net/#comment-4999 , which recieved many good comments.
But when I call
MessageCollection messages = mails.SearchParse("UNSEEN");
I got error message "index and length must refer to a location within the string".
I just call a simple function, so I don't know what's wrong. For more detail, here is my code snippet:
public class MailRepository
{
private Imap4Client _client = null;
public MailRepository(string mailServer, int port, bool ssl, string login, string password)
{
if (ssl)
Client.ConnectSsl(mailServer, port);
else
Client.Connect(mailServer, port);
Client.Login(login, password);
}
public IEnumerable<Message> GetAllMails(string mailBox)
{
return GetMails(mailBox, "ALL").Cast<Message>();
}
public IEnumerable<Message> GetUnreadMails(string mailBox)
{
return GetMails(mailBox, "UNSEEN").Cast<Message>();
}
protected Imap4Client Client
{
get
{
if (_client == null)
_client = new Imap4Client();
return _client;
}
}
private MessageCollection GetMails(string mailBox, string searchPhrase)
{
Mailbox mails = Client.SelectMailbox(mailBox);
MessageCollection messages = mails.SearchParse(searchPhrase);
return messages;
}
}
This error may occur when your mails contain non-ASCII characters. As I don't see an easy fix here and MailSystem.NET is no longer supported, I recommend using an alternative library.
Mailkit seems to be a good option. Also look here for further reference: https://stackoverflow.com/a/23375968
I have the following function which sends mail. The body of the mail has a link (href) in some cases.
public static void SendMail(string from, string to, string subject, string message, bool isHTML = true)
{
if (!String.IsNullOrEmpty(to) && ConfigurationManager.AppSettings["no_mail"] == null)
{
MailMessage mailMessage = new MailMessage(from,
to,
subject,
message);
string bccAddr = ConfigurationManager.AppSettings["managementMailAddress"];
if (!String.IsNullOrEmpty(bccAddr))
mailMessage.Bcc.Add(bccAddr);
bccAddr = ConfigurationManager.AppSettings["debugMailAddress"];
if (!String.IsNullOrEmpty(bccAddr))
mailMessage.Bcc.Add(bccAddr);
if (isHTML)
mailMessage.IsBodyHtml = true;
//mailMessage.BodyEncoding = System.Text.Encoding.UTF8; // avoid 3D?
SmtpClient smtpClient = new SmtpClient(ConfigurationManager.AppSettings["mailHost"]);
smtpClient.Send(mailMessage);
}
}
Yesterday the mail clients started receiving mails with 3D inserted after the equals sign, and so ceased to work properly.
For example, I see "...href=3Dhttp://..." instead of "...href=http://...".
I made a small change in the code with reference to the BCC list, but nothing that should have a serious effect. The web.config file has not changed, other than putting in various mail addresses in configuration variables.
Why are my links broken?
In my application I have a functionality to save and publish articles. So when I click on "Save and Publish" button three things happened:
Published articles get saved in database.
A Notification email goes to a group of users that a new articles is available.
After sending emails page get redirect to "Article Listing" page without showing any success or failure message for an email.
Now Number of users who will receive emails can vary say for e.g 10, 30 50 and so on. I want to send notification emails asynchronously so that page won't get block until all the mails doesn't go to their receptionists.
Given below is a piece of code from "PublishArticle" action method
foreach (string to in emailIds)
{
ArticleNotificationDelegate proc = Email.Send;
IAsyncResult asyncResult = proc.BeginInvoke(subject, body, to, from, cc, null, null, null);
}
Below I have defined a delegate to invoke Send method
private delegate bool ArticleNotificationDelegate (string subject, string body, string to, string from, string cc, string bcc = null);
and this is the code to send an email:
public static bool Send(string subject, string body, string to, string from, string cc, string bcc = null)
{
bool response;
MailMessage mail = new MailMessage();
MailAddress fromAddress = new MailAddress(from);
mail.To.Add(to);
if (!string.IsNullOrWhiteSpace(cc))
{
mail.CC.Add(cc);
}
if (!string.IsNullOrWhiteSpace(bcc))
{
mail.Bcc.Add(bcc);
}
mail.From = fromAddress;
mail.Subject = subject;
mail.Body = body;
mail.IsBodyHtml = true;
mail.Priority = MailPriority.High;
SmtpClient client = new SmtpClient();
try
{
client.Send(mail);
response = true;
}
catch (Exception)
{
response = false;
}
finally
{
client.Dispose();
mail.Dispose();
}
return response;
}
Although this code is working fine but still I want to know that whether my this approach is fine and will not cause any problem in future.
If there is a better approach to accomplish my objective then please suggest me.
Note: As I am using .net framework 4.0 so cannot use the new features of Asyn and await available in 4.5.
Also I had used method client.SendAsync by simply replacing the client.Send and rest of my code in above Send method was same. After this change NO mails were being send and also it did not throw any exception. So I did not go with this change.
I want to send notification emails asynchronously so that page won't get block until all the mails doesn't go to their receptionists.
That's a very dangerous approach, because ASP.NET will feel free to tear down your app domain if there are no active requests. ASP.NET doesn't know that you've queued work to its thread pool (via BeginInvoke). I recommend that you use a solution like HangFire to reliably send email.
Recently somebody answered me on this site, that this method can send email from .net application:
public static void SendEmail(bool isHTML, string toEmail, string fromEmail, string subject, string message)
{
var sm = new SmtpClient("smtp.mail.ru");
sm.Credentials = new NetworkCredential("MyLogin", "MyPass");
var m = new MailMessage(fromEmail, toEmail) { Subject = subject, Body = message };
if (isHTML)
{
m.IsBodyHtml = true;
}
sm.Send(m); // SmtpException
}
It is true. But now I want to use this method from Asp.Net WebService, but I have SmtpException at last string. Why? And do I send email from web service.
So the problem is not with your code, rather the transaction with the SMTP server is failing for some reason. If you have access to the SMTP server, check its logs. Otherwise you might have to use a sniffer like WireShark to figure it out.
To verify this, you can try using a different mail server, assuming you have proper access to that server it should send the mail properly.