Stream does not support reading when calling SendEmailAsync [duplicate] - c#

I am using a service component through ASP.NET MVC.
I would like to send the email in a asynchronous way to let the user do other stuff without having to wait for the sending.
When I send a message without attachments it works fine.
When I send a message with at least one in-memory attachment it fails.
So, I would like to know if it is possible to use an async method with in-memory attachments.
Here is the sending method
public static void Send() {
MailMessage message = new MailMessage("from#foo.com", "too#foo.com");
using (MemoryStream stream = new MemoryStream(new byte[64000])) {
Attachment attachment = new Attachment(stream, "my attachment");
message.Attachments.Add(attachment);
message.Body = "This is an async test.";
SmtpClient smtp = new SmtpClient("localhost");
smtp.Credentials = new NetworkCredential("foo", "bar");
smtp.SendAsync(message, null);
}
}
Here is my current error
System.Net.Mail.SmtpException: Failure sending mail.
---> System.NotSupportedException: Stream does not support reading.
at System.Net.Mime.MimeBasePart.EndSend(IAsyncResult asyncResult)
at System.Net.Mail.Message.EndSend(IAsyncResult asyncResult)
at System.Net.Mail.SmtpClient.SendMessageCallback(IAsyncResult result)
--- End of inner exception stack trace ---
Solution
public static void Send()
{
MailMessage message = new MailMessage("from#foo.com", "to#foo.com");
MemoryStream stream = new MemoryStream(new byte[64000]);
Attachment attachment = new Attachment(stream, "my attachment");
message.Attachments.Add(attachment);
message.Body = "This is an async test.";
SmtpClient smtp = new SmtpClient("localhost");
//smtp.Credentials = new NetworkCredential("login", "password");
smtp.SendCompleted += delegate(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
if (e.Error != null)
{
System.Diagnostics.Trace.TraceError(e.Error.ToString());
}
MailMessage userMessage = e.UserState as MailMessage;
if (userMessage != null)
{
userMessage.Dispose();
}
};
smtp.SendAsync(message, message);
}

Don't use "using" here. You are destroying the memory stream immediately after calling SendAsync, e.g. probably before SMTP gets to read it (since it's async). Destroy your stream in the callback.

I have tried your function and it works even for email with in memory attachments. But here are some remarks:
What type of attachments did you try to send ? Exe ?
Are both sender and receiver on the same email server ?
You should “catch” exception and not just swallow it, than you will get more info about your problem.
What does the exception says ?
Does it work whan you use Send instead of SendAsync ? You are using 'using' clause and closing Stream before email is sent.
Here is good text about this topic:
Sending Mail in .NET 2.0

An extension to the Solution supplied in the original question also correctly cleans up an attachments that may also require disposal.
public event EventHandler EmailSendCancelled = delegate { };
public event EventHandler EmailSendFailure = delegate { };
public event EventHandler EmailSendSuccess = delegate { };
...
MemoryStream mem = new MemoryStream();
try
{
thisReport.ExportToPdf(mem);
// Create a new attachment and put the PDF report into it.
mem.Seek(0, System.IO.SeekOrigin.Begin);
//Attachment att = new Attachment(mem, "MyOutputFileName.pdf", "application/pdf");
Attachment messageAttachment = new Attachment(mem, thisReportName, "application/pdf");
// Create a new message and attach the PDF report to it.
MailMessage message = new MailMessage();
message.Attachments.Add(messageAttachment);
// Specify sender and recipient options for the e-mail message.
message.From = new MailAddress(NOES.Properties.Settings.Default.FromEmailAddress, NOES.Properties.Settings.Default.FromEmailName);
message.To.Add(new MailAddress(toEmailAddress, NOES.Properties.Settings.Default.ToEmailName));
// Specify other e-mail options.
//mail.Subject = thisReport.ExportOptions.Email.Subject;
message.Subject = subject;
message.Body = body;
// Send the e-mail message via the specified SMTP server.
SmtpClient smtp = new SmtpClient();
smtp.SendCompleted += SmtpSendCompleted;
smtp.SendAsync(message, message);
}
catch (Exception)
{
if (mem != null)
{
mem.Dispose();
mem.Close();
}
throw;
}
}
private void SmtpSendCompleted(object sender, AsyncCompletedEventArgs e)
{
var message = e.UserState as MailMessage;
if (message != null)
{
foreach (var attachment in message.Attachments)
{
if (attachment != null)
{
attachment.Dispose();
}
}
message.Dispose();
}
if (e.Cancelled)
EmailSendCancelled?.Invoke(this, EventArgs.Empty);
else if (e.Error != null)
{
EmailSendFailure?.Invoke(this, EventArgs.Empty);
throw e.Error;
}
else
EmailSendSuccess?.Invoke(this, EventArgs.Empty);
}

Related

C# do i need to dispose mail that is sent async?

Using a button to sent mail async and disposing of it in the task itself. Is this the correct way to do it?
Or do I need a catch inside the task and do I need a finally dispose on the button click?
And do I need to dispose of other things like the smtpclient? The mails don't have any attachments they are all just plain text.
private async void closeorder.Click(object sender, EventArgs e)
{
try
{
await SendEmail();
}
catch (exception)
{
MessageBox.Show("mail has not been sent");
}
}
private async Task SendEmail()
{
SmtpClient smtpClient = new SmtpClient();
MailMessage message = new MailMessage();
if (System.IO.File.Exists(emailfile)
{
String[] Lines = System.IO.File.ReadAllLines(emailfile);
foreach (string line in Lines)
message.To.Add(line);
}
else
{
System.IO.File.WriteAllText(emailfile,"");
TextWriter Email = new StreamWriter(emailfile, true);
Email.Writeline("foo#email.com");
Email.close();
}
try
{
MailAdress fromAddress = new MailAddress("goo#email.com","order");
smtpClient.Host "";
smtpClient.Port xx;
smtpClient.credentials = ("""");
smtpClient.DeliveryMethod = SmtpDeliveryMethod.Network;
message.From = fromAddress;
message.Subject = "";
message.IsBodyHtml = True;
message.Body = Mybody();
message.DeliveryNotificationOptions = DeliveryNotificationOptions.OnFailure;
await smtpClient.SendMailAsync(message);
}
finally
{
message.Dispose();
}
}
Sorry if formatting isn't the best typed on mobile.
Both SmtpClient and MailMessage implement the IDisposable interface so you should dispose them.
https://learn.microsoft.com/en-us/dotnet/api/system.net.mail.smtpclient?view=net-6.0
https://learn.microsoft.com/en-us/dotnet/api/system.net.mail.mailmessage?view=net-6.0
The fact it is in a asynchronous flow does not invalidate the fact you need to dispose properly those objects.
But you can avoid to use the try/catch/finally with the using keyword:
using(var smtpClient = new SmtpClient())
using(var message = new MailMessage())
{
// Your code
}
It will generate for you (behing the scene) the try/catch/finally blocks and the call to dispose.

Send raw bulkmail using aws ses ApI interface(Empty required header 'To'.)

Hi I am trying to send bulkmail with attachment using amazon ses. I can able to send mails with attachment but my to-mails are appearing for all the users that I have send. I am trying to add those destination mails in bcc fields but it is throwing an error Empty required header 'To'.
This is what I've already tried:
private static BodyBuilder GetMessageBody()
{
var body = new BodyBuilder()
{
HtmlBody = #"<p>Amazon SES Test body</p>",
TextBody = "Amazon SES Test body",
};
body.Attachments.Add(#"G:\me.jpg");
return body;
}
private static MimeMessage GetMessage()
{
var message = new MimeMessage();
List<string> to = new List<string>(50);
to.Add("xxxxxx#gmail.com");
to.Add("xxxxxx#gmail.com");
message.From.Add(new MailboxAddress(ConfigurationManager.AppSettings["senderaddress"], ConfigurationManager.AppSettings["senderaddress"]));
for (int i = 0; i < to.Count; i++)
{
message.Bcc.Add(new MailboxAddress(string.Empty,to[i]));
//message.To.Add(new MailboxAddress(string.Empty, "xxxxx#gmail.com"));
//message.To.Add(new MailboxAddress(string.Empty, "xxxxxx#gmail.com"));
}
message.Subject = "Amazon SES Test";
message.Body = GetMessageBody().ToMessageBody();
return message;
}
private static MemoryStream GetMessageStream()
{
var stream = new MemoryStream();
GetMessage().WriteTo(stream);
return stream;
}
private void SendEmails()
{
var credentals = new BasicAWSCredentials(ConfigurationManager.AppSettings["AccessKey"], ConfigurationManager.AppSettings["SecretAccessKey"]);
using (var client = new AmazonSimpleEmailServiceClient(credentals, RegionEndpoint.USEast1))
{
var sendRequest = new SendRawEmailRequest { RawMessage = new RawMessage(GetMessageStream()) };
try
{
var response = client.SendRawEmail(sendRequest);
}
catch (Exception e)
{
}
}
}
You need at least 1 email address in to 'To' field. Perhaps send the email to yourself and add the others as a BCC.
Instead of using the BCC header to send out bulk mails, IMHO you should send one email with a clear "to" header to each of the recipients. So instead looping through recipients and adding them to BCC you should rather create and send a message there.

BCC email address exception handling in task that sends out email

I setup a task to send an Email asynchronously and trying to handle exception against an Email Address that has been failed for sending an email. It successfully log out the exception but I wonder if I am able to log out the Email Address from Bcc list for which causes the exception to occur. Below is my code, any suggestion on this would be helpful
public void SendEmail(string from, string copyToSender, List<string> bccRecipient, string subject, string body, SmtpSection smtpSection)
{
var context = HttpContext.Current;
if (smtpSection != null)
{
Task.Factory.StartNew(() =>
{
var mailMessage = new MailMessage();
mailMessage.From = new MailAddress(from, smtpSection.Network.TargetName);
mailMessage.Subject = subject;
mailMessage.Body = body;
mailMessage.IsBodyHtml = true;
myMailMessage = mailMessage;
//send emails to which to Bcc'd including the From Person Email
myMailMessage.Bcc.Add(copyToSender);
foreach (var r in bccRecipient)
{
myMailMessage.Bcc.Add(r);
}
//incorrect email address added to log out the exception against it
myMailMessage.Bcc.Add("foo");
using (var client = new SmtpClient())
{
client.Host = smtpSection.Network.Host;
client.EnableSsl = smtpSection.Network.EnableSsl;
client.Port = Convert.ToInt32(smtpSection.Network.Port);
client.Credentials = new NetworkCredential(smtpSection.Network.UserName,
smtpSection.Network.Password);
client.Send(mailMessage);
}
}).ContinueWith(tsk =>
{
var recipientEmail=//how to figure this out??
//something broke
var flattened = tsk.Exception.Flatten();
flattened.Handle(ex =>
{
_mailLogger.LogError
(recipientEmail, context.Request.UrlReferrer.OriginalString, ex.Message);
return true;
});
}, TaskContinuationOptions.OnlyOnFaulted); ;
}
}

Sending Email asynchronously in ASP.NET C#

I was wondering if theres a way to send email asynchronously in asp.net c#. I've used the following code to send the emails:
if (checkObavijest.Checked)
{
List<hsp_Kupci_Newsletter_Result> lista = ServisnaKlasa.KupciNewsletter();
for (int i = 0; i < lista.Count; i++)
{
MailMessage mail = new MailMessage();
mail.From = new MailAddress("*******");
mail.To.Add(lista[i].Email);
mail.Subject = "Subject";
mail.SubjectEncoding = Encoding.UTF8;
mail.BodyEncoding = Encoding.UTF8;
mail.IsBodyHtml = true;
mail.Priority = MailPriority.High;
mail.Body = "Some message";
SmtpClient smtpClient = new SmtpClient();
Object state = mail;
smtpClient.Credentials = new NetworkCredential("****#gmail.com", "****");
smtpClient.Port = 587;
smtpClient.Host = "smtp.gmail.com";
smtpClient.EnableSsl = true;
smtpClient.SendCompleted += new SendCompletedEventHandler(smtpClient_SendCompleted);
try
{
smtpClient.SendAsync(mail, state);
}
catch (Exception ex)
{
}
}
void smtpClient_SendCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
MailMessage mail = e.UserState as MailMessage;
if (!e.Cancelled && e.Error != null)
{
string poruka = "Emailovi poslani!";
ClientScript.RegisterStartupScript(this.GetType(), "myalert", "alert('" + poruka + "');", true);
}
}
But when I tick the option for sending emails, the page loads like 40 seconds, then after 40 seconds emails are sent then. As you can see I'm pulling out like 20-30 emails currently out of my DB. I thought this was the correct way to send mails asynchronously, but apparently its not... How can I push the email sending to another thread so it doesn't affects the user who is using this page? Can someone help me out with this ? Thanks !
The reason is mentioned here on the MSDN documentation on SmtpClient.SendAsync
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.
You could call SendAsync using ThreadPool thru a wrapped method call. This would give you something like a fire-and-forget scenario.
Something like this:
public void SendViaThread(System.Net.Mail.MailMessage message) {
try {
System.Threading.ThreadPool.QueueUserWorkItem(SendViaAsync, message);
} catch (Exception ex) {
throw;
}
}
private void SendViaAsync(object stateObject) {
System.Net.Mail.SmtpClient smtp = new System.Net.Mail.SmtpClient();
System.Net.Mail.MmailMessage message = (MmailMessage)stateObject;
...
smtp.Credentials = new NetworkCredential("...");
smtp.Port = 587;
smtp.Host = "...";
...
smtp.SendCompleted += new SendCompletedEventHandler(smtpClient_SendCompleted);
...
smtp.Send(message);
}
And you would call SendViaThread(mail) in your code. So, your loop now becomes:
for (int i = 0; i < lista.Count; i++) {
MailMessage mail = new MailMessage();
mail.From = new MailAddress("...");
mail.To.Add(lista[i].Email);
mail.Subject = "...";
...
mail.Body = "...";
SendViaThread(mail);
}
If you want to not have your main thread waiting, try tossing it in a Task.
Task.Factory.StartNew( () => {
// do mail stuff here
});
Be aware, though that each time you spawn a task, it'll spawn a new (or re-use) a thread that your system has made. If you're firing off 30 e-mails, you have the potential of firing off a lot of threads (the system has a programmable cap, too). Task.Factory.StartNew is a very simple way to do a fire-and-forget process.
http://msdn.microsoft.com/en-us/library/dd321439%28v=vs.110%29.aspx
(in this example, the code is also keeping a collection of Tasks, which is nice if you care to manage them -- but in reality, you only need the Task.Factory.StartNew(()=>{ bit if you want to fire-and-forget. Just be careful because you don't want to have orphaned threads pegging your memory usage.

Send Attachments with Amazon-SES

I'm searching for an working C# example to send attachments with Amazon-SES.
After reading that Amazon-SES now supports sending attachments I was searching for an C# example but was unable to find one.
I think that using AWS SDK for .NET and MimeKit is very easy and clean solution. You can send e-mails with attachments via SES API (instead of SMTP).
You can write MimeMessage directly to MemoryStream and then use it with SES SendRawEmail:
using Amazon.SimpleEmail;
using Amazon.SimpleEmail.Model;
using Amazon;
using Amazon.Runtime;
using MimeKit;
private static BodyBuilder GetMessageBody()
{
var body = new BodyBuilder()
{
HtmlBody = #"<p>Amazon SES Test body</p>",
TextBody = "Amazon SES Test body",
};
body.Attachments.Add(#"c:\attachment.txt");
return body;
}
private static MimeMessage GetMessage()
{
var message = new MimeMessage();
message.From.Add(new MailboxAddress("Foo Bar", "foo#bar.com"));
message.To.Add(new MailboxAddress(string.Empty, "foobar#example.com"));
message.Subject = "Amazon SES Test";
message.Body = GetMessageBody().ToMessageBody();
return message;
}
private static MemoryStream GetMessageStream()
{
var stream = new MemoryStream();
GetMessage().WriteTo(stream);
return stream;
}
private void SendEmails()
{
var credentals = new BasicAWSCredentials("<aws-access-key>", "<aws-secret-key>");
using (var client = new AmazonSimpleEmailServiceClient(credentals, RegionEndpoint.EUWest1))
{
var sendRequest = new SendRawEmailRequest { RawMessage = new RawMessage(GetMessageStream()) };
try
{
var response = client.SendRawEmail(sendRequest);
Console.WriteLine("The email was sent successfully.");
}
catch (Exception e)
{
Console.WriteLine("The email was not sent.");
Console.WriteLine("Error message: " + e.Message);
}
}
}
public Boolean SendRawEmail(String from, String to, String cc, String Subject, String text, String html, String replyTo, string attachPath)
{
AlternateView plainView = AlternateView.CreateAlternateViewFromString(text, Encoding.UTF8, "text/plain");
AlternateView htmlView = AlternateView.CreateAlternateViewFromString(html, Encoding.UTF8, "text/html");
MailMessage mailMessage = new MailMessage();
mailMessage.From = new MailAddress(from);
List<String> toAddresses = to.Replace(", ", ",").Split(',').ToList();
foreach (String toAddress in toAddresses)
{
mailMessage.To.Add(new MailAddress(toAddress));
}
List<String> ccAddresses = cc.Replace(", ", ",").Split(',').Where(y => y != "").ToList();
foreach (String ccAddress in ccAddresses)
{
mailMessage.CC.Add(new MailAddress(ccAddress));
}
mailMessage.Subject = Subject;
mailMessage.SubjectEncoding = Encoding.UTF8;
if (replyTo != null)
{
mailMessage.ReplyToList.Add(new MailAddress(replyTo));
}
if (text != null)
{
mailMessage.AlternateViews.Add(plainView);
}
if (html != null)
{
mailMessage.AlternateViews.Add(htmlView);
}
if (attachPath.Trim() != "")
{
if (System.IO.File.Exists(attachPath))
{
System.Net.Mail.Attachment objAttach = new System.Net.Mail.Attachment(attachPath);
objAttach.ContentType = new ContentType("application/octet-stream");
System.Net.Mime.ContentDisposition disposition = objAttach.ContentDisposition;
disposition.DispositionType = "attachment";
disposition.CreationDate = System.IO.File.GetCreationTime(attachPath);
disposition.ModificationDate = System.IO.File.GetLastWriteTime(attachPath);
disposition.ReadDate = System.IO.File.GetLastAccessTime(attachPath);
mailMessage.Attachments.Add(objAttach);
}
}
RawMessage rawMessage = new RawMessage();
using (MemoryStream memoryStream = ConvertMailMessageToMemoryStream(mailMessage))
{
rawMessage.WithData(memoryStream);
}
SendRawEmailRequest request = new SendRawEmailRequest();
request.WithRawMessage(rawMessage);
request.WithDestinations(toAddresses);
request.WithSource(from);
AmazonSimpleEmailService ses = AWSClientFactory.CreateAmazonSimpleEmailServiceClient(ConfigurationManager.AppSettings.Get("AccessKeyId"), ConfigurationManager.AppSettings.Get("SecretKeyId"));
try
{
SendRawEmailResponse response = ses.SendRawEmail(request);
SendRawEmailResult result = response.SendRawEmailResult;
return true;
}
catch (AmazonSimpleEmailServiceException ex)
{
return false;
}
}
public static MemoryStream ConvertMailMessageToMemoryStream(MailMessage message)
{
Assembly assembly = typeof(SmtpClient).Assembly;
Type mailWriterType = assembly.GetType("System.Net.Mail.MailWriter");
MemoryStream fileStream = new MemoryStream();
ConstructorInfo mailWriterContructor = mailWriterType.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { typeof(Stream) }, null);
object mailWriter = mailWriterContructor.Invoke(new object[] { fileStream });
MethodInfo sendMethod = typeof(MailMessage).GetMethod("Send", BindingFlags.Instance | BindingFlags.NonPublic);
sendMethod.Invoke(message, BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { mailWriter, true }, null);
MethodInfo closeMethod = mailWriter.GetType().GetMethod("Close", BindingFlags.Instance | BindingFlags.NonPublic);
closeMethod.Invoke(mailWriter, BindingFlags.Instance | BindingFlags.NonPublic, null, new object[] { }, null);
return fileStream;
}
Found that here
Update: A method signature has changed in .NET 4.5, which breaks the above:
Getting System.Net.Mail.MailMessage as a MemoryStream in .NET 4.5 beta
This is a very simple implementation using MimeKit
using Amazon;
using Amazon.SimpleEmail;
using Amazon.SimpleEmail.Model;
using MimeKit;
using System.IO;
namespace SendEmailWithAttachments
{
class Program
{
static void Main(string[] args)
{
//Remember to enter your (AWSAccessKeyID, AWSSecretAccessKey) if not using and IAM User with credentials assigned to your instance and your RegionEndpoint
using (var client = new AmazonSimpleEmailServiceClient("YourAWSAccessKeyID", "YourAWSSecretAccessKey", RegionEndpoint.USEast1))
using (var messageStream = new MemoryStream())
{
var message = new MimeMessage();
var builder = new BodyBuilder() { TextBody = "Hello World" };
message.From.Add(new MailboxAddress("FROMADDRESS#TEST.COM"));
message.To.Add(new MailboxAddress("TOADDRESS#TEST.COM"));
message.Subject = "Hello World";
//I'm using the stream method, but you don't have to.
using (FileStream stream = File.Open(#"Attachment1.pdf", FileMode.Open)) builder.Attachments.Add("Attachment1.pdf", stream);
using (FileStream stream = File.Open(#"Attachment2.pdf", FileMode.Open)) builder.Attachments.Add("Attachment2.pdf", stream);
message.Body = builder.ToMessageBody();
message.WriteTo(messageStream);
var request = new SendRawEmailRequest()
{
RawMessage = new RawMessage() { Data = messageStream }
};
client.SendRawEmail(request);
}
}
}
}
I have the code in my repository https://github.com/gianluis90/amazon-send-email
You can setup IIS SMTP to relay through SES as well.
You need to install stunnel and set it up
Then you can just set the IIS SMTP Smart Host and some other options and it will relay your email through SES.
Instructions from above linked gist:
Instructions taken from Amazon's docs and modified as necessary.
1. Install stunnel:
Download from stunnel's download page
Run installer w/ default options, create self signed cert by answering questions
Open the c:\program files (x86)\stunnel\stunnel.conf file in notepad
Clear all the server configs (under Example SSL server mode services section, won't have a client = yes line)
Create a new client config:
[smtp-tls-wrapper]
accept = 127.0.0.1:2525
client = yes
connect = email-smtp.us-east-1.amazonaws.com:465
Start stunnel.exe and ensure no errors are reported (you will get a little systray icon)
If it succeeds you can optionally install as a service, by running stunnel.exe -install at command line (note this installs the service but doesn't start it, so start it)
Test the connection, at cmd line run telnet localhost 2525 and you should see an SMTP response from Amazon's server (if telnet isn't installed, add the feature in Server Manager / Features / Add Feature)
2. Configure IIS SMTP
Set Smart Host as [127.0.0.1] (include the brackets)
In the Outbound Connections section, set outgoing port to 2525 (like the stunnel.conf file)
In the Outbound Security section, set authentication information to your Amazon SMTP credentials, set it to basic authentication (NOTE: DO NOT CHECK THE TLS CHECKBOX)
I'm not sure if this is what you are looking for, but it's the only resource I've been able to find on the subject. I would love a better answer to this question, too.
http://docs.amazonwebservices.com/ses/latest/DeveloperGuide/
It states how to use it, but is very cryptic, at least to me.
Any better guides out there?
I also need help with this, but so far I've found you need to send a multipart MIME message, with the attachment encoded in base64.
I think you need to follow the instructions on this link. Amazon doesn't let you add attachments or other more complicated message types (iCalendar events). Essentially you need to handcraft the message body by string building and manipulation.
Currently I do this for iCalendar format emails on a legacy system. It's a bit of a pain in the ass, but if you read RFC 2822, it tells you pretty clearly what the format is. Special things to pay attention to:
Base64 encoding of the data
MIME types
Make sure your multipart boundaries match up (and are unique)
Finicky problems with the number of line breaks (\n) after certain lines
Best of luck. I don't know if there is an open library that will do this sort of thing for you in C#. If you can find one then try use it, because dealing with the intricacies of the RFC should have a medical notice for increased blood pressure and hair loss.

Categories