133

I am writing a program to attach a file to email. Currently I am saving file using FileStream into disk, and then I use

System.Net.Mail.MailMessage.Attachments.Add(
    new System.Net.Mail.Attachment("file name")); 

I do not want to store file in disk, I want to store file in memory and from memory stream pass this to Attachment.

wonea
  • 4,783
  • 17
  • 86
  • 139
Zain Ali
  • 15,535
  • 14
  • 95
  • 108

9 Answers9

126

Here is the sample code.

System.IO.MemoryStream ms = new System.IO.MemoryStream();
System.IO.StreamWriter writer = new System.IO.StreamWriter(ms);
writer.Write("Hello its my sample file");
writer.Flush();
writer.Dispose();
ms.Position = 0;

System.Net.Mime.ContentType ct = new System.Net.Mime.ContentType(System.Net.Mime.MediaTypeNames.Text.Plain);
System.Net.Mail.Attachment attach = new System.Net.Mail.Attachment(ms, ct);
attach.ContentDisposition.FileName = "myFile.txt";

// I guess you know how to send email with an attachment
// after sending email
ms.Close();

Edit 1

You can specify other file types by System.Net.Mime.MimeTypeNames like System.Net.Mime.MediaTypeNames.Application.Pdf

Based on Mime Type you need to specify correct extension in FileName for instance "myFile.pdf"

hsimah
  • 1,265
  • 2
  • 20
  • 37
Waqas Raja
  • 10,802
  • 4
  • 33
  • 38
105

A bit of a late entry - but hopefully still useful to someone out there:-

Here's a simplified snippet for sending an in-memory string as an email attachment (a CSV file in this particular case).

using (var stream = new MemoryStream())
using (var writer = new StreamWriter(stream))    // using UTF-8 encoding by default
using (var mailClient = new SmtpClient("localhost", 25))
using (var message = new MailMessage("me@example.com", "you@example.com", "Just testing", "See attachment..."))
{
    writer.WriteLine("Comma,Separated,Values,...");
    writer.Flush();
    stream.Position = 0;     // read from the start of what was written
    
    message.Attachments.Add(new Attachment(stream, "filename.csv", "text/csv"));
    
    mailClient.Send(message);
}

The StreamWriter and underlying stream must not be disposed until after the message has been sent (to avoid ObjectDisposedException: Cannot access a closed Stream).

tranquil tarn
  • 1,606
  • 1
  • 13
  • 11
  • 33
    For any newcomers, the key for me was setting the `stream.position = 0;` – m.t.bennett Dec 09 '13 at 02:47
  • 4
    +1 for appropriate use of using()'s -- something that always seems to be missing from examples and snippets online (including the accepted answer to this question). – Jay Querido Aug 15 '14 at 20:18
  • 2
    Thanks to @m.t.bennet that was also my problem - setting `stream.Position=0`. – Icarus Nov 17 '14 at 22:59
  • What does setting the MIME type actually do here? Something for the e-mail client app? – xr280xr Apr 28 '20 at 03:46
  • @xr280xr - Correct. This parameter is not actually required, but including it should help the recipient's email client handle the attachment sensibly. https://learn.microsoft.com/en-us/dotnet/api/system.net.mime.contenttype?view=netstandard-2.1#remarks – tranquil tarn Jun 10 '20 at 12:02
33

Since I couldn't find confirmation of this anywhere, I tested if disposing of the MailMessage and/or the Attachment object would dispose of the stream loaded into them as I expected would happen.

It does appear with the following test that when the MailMessage is disposed, all streams used to create attachments will also be disposed. So as long as you dispose your MailMessage the streams that went into creating it do not need handling beyond that.

MailMessage mail = new MailMessage();
//Create a MemoryStream from a file for this test
MemoryStream ms = new MemoryStream(File.ReadAllBytes(@"C:\temp\test.gif"));

mail.Attachments.Add(new System.Net.Mail.Attachment(ms, "test.gif"));
if (mail.Attachments[0].ContentStream == ms) Console.WriteLine("Streams are referencing the same resource");
Console.WriteLine("Stream length: " + mail.Attachments[0].ContentStream.Length);

//Dispose the mail as you should after sending the email
mail.Dispose();
//--Or you can dispose the attachment itself
//mm.Attachments[0].Dispose();

Console.WriteLine("This will throw a 'Cannot access a closed Stream.' exception: " + ms.Length);
Thymine
  • 8,775
  • 2
  • 35
  • 47
  • Damn, that's clever. One line of code, and you could add an image file to an email as an attachment. Great tip! – Mike Gledhill Mar 12 '15 at 08:47
  • I wish this was actually documented. Your test/verification of this behavior is useful, but without being in the official documentation its difficult to trust that this will always be the case. Anyway, thanks for testing. – Lucas Oct 23 '16 at 19:08
  • Good Effort and Research @thymine – vibs2006 Jan 15 '17 at 12:09
  • 2
    it is sort of documented, if reference source counts. [mailmessage.dispose](https://referencesource.microsoft.com/#System/net/System/Net/mail/MailMessage.cs,280) calls [attachments.dispose](https://referencesource.microsoft.com/#System/net/System/Net/mail/AttachmentCollection.cs,19) which in turns calls [dispose on each attachment](https://referencesource.microsoft.com/#System/net/System/Net/mail/Attachment.cs,60) which, in mimepart, [closes the stream](https://referencesource.microsoft.com/#System/net/System/Net/mail/MimePart.cs,28). – Cee McSharpface Sep 07 '18 at 10:32
  • Thank you. I was thinking that the mail message ought to do that and need it to because I am creating my messages in different classes than where the message is actually sent. – xr280xr Apr 28 '20 at 00:27
30

If you actually want to add a .pdf, I found it necessary to set the position of the memory stream to Zero.

var memStream = new MemoryStream(yourPdfByteArray);
memStream.Position = 0;
var contentType = new System.Net.Mime.ContentType(System.Net.Mime.MediaTypeNames.Application.Pdf);
var reportAttachment = new Attachment(memStream, contentType);
reportAttachment.ContentDisposition.FileName = "yourFileName.pdf";
mailMessage.Attachments.Add(reportAttachment);
Jason Dimmick
  • 400
  • 4
  • 12
14

If all you're doing is attaching a string, you could do it in just 2 lines:

mail.Attachments.Add(Attachment.CreateAttachmentFromString("1,2,3", "text/csv");
mail.Attachments.Last().ContentDisposition.FileName = "filename.csv";

I wasn't able to get mine to work using our mail server with StreamWriter.
I think maybe because with StreamWriter you're missing a lot of file property information and maybe our server didn't like what was missing.
With Attachment.CreateAttachmentFromString() it created everything I needed and works great!

Otherwise, I'd suggest taking your file that is in memory and opening it using MemoryStream(byte[]), and skipping the StreamWriter all together.

MikeTeeVee
  • 18,543
  • 7
  • 76
  • 70
  • 1
    This method doesn't require you to keep the stream open until you send, which was very helpful for the way I was creating email messages. Thank you! – Shelby115 Feb 02 '21 at 14:51
  • 1
    very useful if you need to handle multiple attachments – Jag Aug 15 '22 at 15:28
3

I landed on this question because I needed to attach an Excel file I generate through code and is available as MemoryStream. I could attach it to the mail message but it was sent as 64Bytes file instead of a ~6KB as it was meant. So, the solution that worked for me was this:

MailMessage mailMessage = new MailMessage();
Attachment attachment = new Attachment(myMemorySteam, new ContentType(MediaTypeNames.Application.Octet));

attachment.ContentDisposition.FileName = "myFile.xlsx";
attachment.ContentDisposition.Size = attachment.Length;

mailMessage.Attachments.Add(attachment);

Setting the value of attachment.ContentDisposition.Size let me send messages with the correct size of attachment.

ilForna
  • 41
  • 1
  • 4
2

use OTHER OPEN memorystream:

example for lauch pdf and send pdf in MVC4 C# Controller

        public void ToPdf(string uco, int idAudit)
    {
        Response.Clear();
        Response.ContentType = "application/octet-stream";
        Response.AddHeader("content-disposition", "attachment;filename= Document.pdf");
        Response.Buffer = true;
        Response.Clear();

        //get the memorystream pdf
        var bytes = new MisAuditoriasLogic().ToPdf(uco, idAudit).ToArray();

        Response.OutputStream.Write(bytes, 0, bytes.Length);
        Response.OutputStream.Flush();

    }


    public ActionResult ToMail(string uco, string filter, int? page, int idAudit, int? full) 
    {
        //get the memorystream pdf
        var bytes = new MisAuditoriasLogic().ToPdf(uco, idAudit).ToArray();

        using (var stream = new MemoryStream(bytes))
        using (var mailClient = new SmtpClient("**YOUR SERVER**", 25))
        using (var message = new MailMessage("**SENDER**", "**RECEIVER**", "Just testing", "See attachment..."))
        {

            stream.Position = 0;

            Attachment attach = new Attachment(stream, new System.Net.Mime.ContentType("application/pdf"));
            attach.ContentDisposition.FileName = "test.pdf";

            message.Attachments.Add(attach);

            mailClient.Send(message);
        }

        ViewBag.errMsg = "Documento enviado.";

        return Index(uco, filter, page, idAudit, full);
    }
Ángel Ibáñez
  • 329
  • 1
  • 6
0

I am assuming that when you dispose the MailMessage, the Memory Steams get disposed too, as mentioned above by one of you chaps. A late entry, and was having memory stream exception of stream too long. Of course then I wasn't disposing my mailmessage. So this may help, that I am sending multi-file Attachments:

    public void SendEmail(EmailDao mail)
    {
        try
        {
            //Send Email:
            var networkCredentials = new NetworkCredential { UserName = _businessSmtpDetails.GetSection("NetworkUsername").Value, Password = _businessSmtpDetails.GetSection("NetworkPassword").Value };
            var smtpServer = new SmtpClient(_businessSmtpDetails.GetSection("SmtpServer").Value);
            smtpServer.Credentials = networkCredentials;

            var mailMessage = new MailMessage();

            mailMessage.From = new MailAddress(_businessSmtpDetails.GetSection("BusinessEmail").Value);
            mailMessage.Body = mail.EmailBody;
            mailMessage.Subject = @"From " + mail.EmailFrom + " " + mail.EmailSubject;
            var fileStream = new FileInfo("/images/attachement");
            MemoryStream memoryStream = null;
            if (mail.Attachment != null)
            {
                memoryStream = new MemoryStream();
                ReadFileAttachment(mail.Attachment, memoryStream);
                memoryStream.Seek(0, SeekOrigin.Begin);
                System.Net.Mime.ContentType ct = new System.Net.Mime.ContentType(System.Net.Mime.MediaTypeNames.Text.Plain);
                var attached = new Attachment(memoryStream, mail.Attachment.FileName, ct.MediaType);
                mailMessage.Attachments.Add(attached);
            }
            if (mail.Attachments != null && mail.Attachments.Length > 0)
            {
                foreach (var attachment in mail.Attachments)
                {
                    memoryStream = new MemoryStream();
                    ReadFileAttachment(attachment, memoryStream);
                    memoryStream.Seek(0, SeekOrigin.Begin);
                    System.Net.Mime.ContentType ct = new System.Net.Mime.ContentType(System.Net.Mime.MediaTypeNames.Text.Plain);
                    var attached = new Attachment(memoryStream, mail.Attachment.FileName, ct.MediaType);
                    mailMessage.Attachments.Add(attached);
                }
            }
            mail.EmailTo += string.Format(";{0}", _businessSmtpDetails.GetSection("BusinessEmail").Value);
            Array.ForEach<string>(mail.EmailTo.Split(new char[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries), (p) =>
            {
                mailMessage.To.Add(p);
            });
            smtpServer.Send(mailMessage);
            memoryStream.Close();
            mailMessage.Dispose();
        }
        catch (Exception e)
        {
            throw e;
        }
    }

    private MemoryStream ReadFileAttachment(IFormFile attacment, MemoryStream memoryStream)
    {
        var bytes = new byte[4096];
        var bytesRead = 0;
        while ((bytesRead = attacment.OpenReadStream().Read(bytes, 0, bytes.Length)) > 0)
        {
            memoryStream.Write(bytes, 0, bytesRead);
            memoryStream.Flush();
        }
        return memoryStream;
    }
  • Actually I shouldn't be closing the memory stream reference at the end. Just a point I should make. That statement shouldn't be there nor the use of FileInfo - as it was a remnant from trying to bypass the usage of memory stream as there are known bugs. I still get stream too long exception error, even after correcting the code. – Martin Alex Okello May 12 '23 at 19:15
  • Unfortunately still throwing an exception of Stream too Long. This is a known bug with Microsoft .net core all versions. They said they fixed as it existed in .net core 2.1, but people are still finding this error up to .net 6/ – Martin Alex Okello May 12 '23 at 19:22
-8

I think this code will help you:

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Net.Mail;

public partial class _Default : System.Web.UI.Page
{
  protected void Page_Load(object sender, EventArgs e)
  {

  }

  protected void btnSubmit_Click(object sender, EventArgs e)
  {
    try
    {
      MailAddress SendFrom = new MailAddress(txtFrom.Text);
      MailAddress SendTo = new MailAddress(txtTo.Text);

      MailMessage MyMessage = new MailMessage(SendFrom, SendTo);

      MyMessage.Subject = txtSubject.Text;
      MyMessage.Body = txtBody.Text;

      Attachment attachFile = new Attachment(txtAttachmentPath.Text);
      MyMessage.Attachments.Add(attachFile);

      SmtpClient emailClient = new SmtpClient(txtSMTPServer.Text);
      emailClient.Send(MyMessage);

      litStatus.Text = "Message Sent";
    }
    catch (Exception ex)
    {
      litStatus.Text = ex.ToString();
    }
  }
}
Daniel
  • 10,864
  • 22
  • 84
  • 115
r12
  • 59
  • 4
  • 5
    -1 This answer loads the attachment from disk using the Attachment(string fileName) constructor. The OP specifically states he does not want to load from disk. Also, this is just the code copy-pasted from the link in Red Swan's answer. – Walter Stabosz Jul 12 '13 at 18:09
  • attachFile stream is not disposed as well – Sameh Dec 02 '19 at 19:35