I have a simple C# app that send SMTP emails (using System.Net.Mail classes). After sending (emailing) a MailMessage object I want to iterate through the list of the attachments and delete the original files associated with those attachments... but I am having a hard time finding the full file path associated with each attachment - without keeping my own collection of attachment filepaths. There has got to be a good way to extract the full file path from the attachment object.
I know this has got to be simple, but I am spending way to much time on this..time to ask others.
If you adding your attachments through the Attachment constructor with filePath argument, these attachments can be retrieved through ContentStream property and will be of type FileStream. Here is how you can get file names of the files attached:
var fileNames = message.Attachments
.Select(a => a.ContentStream)
.OfType<FileStream>()
.Select(fs => fs.Name);
But don't forget to dispose MailMessage object first, otherwise you won't be able to delete these attachments:
IEnumerable<string> attachments = null;
using (var message = new MailMessage())
{
...
attachments = message.Attachments
.Select(a => a.ContentStream)
.OfType<FileStream>()
.Select(fs => fs.Name);
}
foreach (var attachment in attachments )
{
File.Delete(attachment);
}
You can
Read Attachment.ContentStream
If you now have a StreamReader or similar, use the BaseStream property to try and find the inner FileStream
Read FileStream.Name
but bear in mind that the mail message (and hence attachments and their streams) may not get collected or cleaned up immediately, so you may not be able to delete the file straight away. You might do better subclassing Attachment and both record the filename and subclass Dispose (to execute after the base dispose) to do the delete if you really do need to do things this way.
It's generally easiest to take a slightly different tack and attach via a memorystream rather than a file. That way you avoid all the issues around saving the files to disk and cleaning them up afterwards.
Short article here on that.
Related
I'm working with C# on Windows servers for a web application stored on the IIS Server.
I would like to create an eml file from :
an html content (string)
some attachments that are loaded in memory
a string subject
string recipients
string sender
The main problem is that I am not allowed to store files on the host server (not even in a temporary directory or if I delete them after).
I saw many threads explaining how to create an eml file with the help of SmtpClient. But we always need to use a directory to save the file.
Do someone knows a way to do that ? Or to create a directory in memory (which seems undoable) ?
Thanks for everyone who will read me
[EDIT]
Using jstedfast's answer below and the Mime documentation, I could figure a way. Here is a POC in case someone needs it later.
var message = new MimeMessage();
message.From.Add(new MailboxAddress("Joey", "joey#friends.com"));
message.To.Add(new MailboxAddress("Alice", "alice#wonderland.com"));
message.Subject = "How you doin?";
var builder = new BodyBuilder();
// Set the plain-text version of the message text
builder.TextBody = #"Hey Alice,
What are you up to this weekend? Monica is throwing one of her parties on
Saturday and I was hoping you could make it.
Will you be my +1?
-- Joey
";
// We may also want to attach a calendar event for Monica's party...
builder.Attachments.Add("test.pdf", attachmentByteArray);
// Now we just need to set the message body and we're done
message.Body = builder.ToMessageBody();
using (var memory = new MemoryStream())
{
message.WriteTo(memory);
}
Look into using MimeKit.
You can write the MimeMessage objects to any type of stream that you want, including a MemoryStream.
I have a stream of byte[] which I write to a temporary file and then I send it to another method which attaches it to an email. I then want to delete the temporary folder. The code snippet I am using is as follows.
byte[] blackboxBytes = Convert.FromBase64String(backBoxBase64);
uniqueTempFolder = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()));
zipFilePath = Path.Combine(uniqueTempFolder.FullName, "BlackBox.zip");
File.WriteAllBytes(zipFilePath, blackboxBytes);
sendEmail (deviceFQN, message, ZipFilePath);
s_Log.Warn("Email sent");
//recursive delete of the whole folder
uniqueTempFolder.Delete(true);
s_Log.Warn("In BB zipFilePath after delete");
When I run, the email is getting sent and I get the log "Email sent". but after that I get an error message and the temporary directory is not deleted.
IOError: [Errno 32] The process cannot access the file 'BlackBox.zip' because it is being used by another process.
I am deleting the directory only after the email method finishes processing. So I don't know why the folder is still being processed. Any pointers will be greatly appreciated.
Also I have no access to the sendEmail method, so how can I solve this....can I probably put my code in a synchronous block or something
The retun type of sendEmail is void...I cannot modify sendEmail , but I see it has a lock when it sends the email(dispatchEmailTask).......
lock (m_QueueLock) { m_DispatchEmailTasks.Enqueue (dispatchEmailTask);}
so in my code, how can I wait for it to complete before I delete the file?
There was no way to do this because the sendEmail sent the emails to a queue and there was no handler returned to let me know the action was competed. So in the end I created a cleanup job that ran daily to clean up the files.
This is because you are working with Streams which are normally In-Memory and normally employ locks for purposes of single calls. This means you can only use a stream once and have to re-create it again in case you want to perform additional operations on the file or directory.
The problem in your code is when you are writing the zip file to the directory, the stream does not get release. A Using statement coupled up together with a StreamWriter all under System.IO will help you in this as shown in the code below.
byte[] blackboxBytes = Convert.FromBase64String(backBoxBase64);
var uniqueTempFolder =
Directory.CreateDirectory(Path.Combine(Path.GetTempPath(),
Path.GetRandomFileName()));
var zipFilePath = Path.Combine(uniqueTempFolder.FullName,
"BlackBox.zip");
using (StreamWriter writer = new StreamWriter(zipFilePath))
{
writer.Write(blackboxBytes);
}
sendEmail(deviceFQN, message, ZipFilePath);
s_Log.Warn("Email sent");
//recursive delete of the whole folder
uniqueTempFolder.Delete(true);
s_Log.Warn("In BB zipFilePath after delete");
I am using MailKit/MimeKit 1.2.7 (latest NuGet version).
I am using ImapClient to receive emails that can have diverse attachments (images, text files, binary files, etc).
MimeMessage's Attachment property helps me access all these attachments --- unless the emails are being sent with Apple Mail and contain images (it seems that Apple Mail does not attach images with Content-Disposition "attachment" (read here ... comment from Jeffrey Stedfast at the very bottom).
Embedded images are not listed in the Attachments collection.
What are my options? Do I really have to traverse the body parts one by one and see what's inside? Or is there an easier solution?
The Working with Messages document lists a few ways of examining the MIME parts within a message, but another simple option might be to use the BodyParts property on the MimeMessage.
To start, let's take a look at how the MimeMessage.Attachments property works:
public IEnumerable<MimeEntity> Attachments {
get { return BodyParts.Where (x => x.IsAttachment); }
}
As you've already noted, the reason that this property doesn't return the attachments you are looking for is because they do not have Content-Disposition: attachment which is what the MimeEntity.IsAttachment property is checking for.
An alternate rule might be to check for a filename parameter.
var attachments = message.BodyParts.Where (x => x.ContentDisposition != null && x.ContentDisposition.FileName != null).ToList ();
Or maybe you could say you just want all images:
var images = message.BodyParts.OfType<MimePart> ().Where (x => x.ContentType.IsMimeType ("image", "*")).ToList ();
Hope that gives you some ideas on how to get the items you want.
Folks,
Using the System.Diagnostics.Process.Start("mailto: method of creating an email, is there a way to add a dynamic attachment (not a saved file) to the email?
I'm pretty much doing the same as this the person in this question but no-one has answered using the mailto: method.
Im just wondering if its possible, and how to do it.
I've tried this but to no avail:
System.IO.MemoryStream ms = new System.IO.MemoryStream(generatedReport.DocumentBytes);
System.Net.Mime.ContentType ct = new System.Net.Mime.ContentType(System.Net.Mime.MediaTypeNames.Application.Pdf);
Attachment attachment = new Attachment(ms, ct);
attachment.ContentDisposition.FileName = "output.pdf";
System.Diagnostics.Process.Start("mailto:myemail &SUBJECT=Test Subject BODY=Body Text&Attachment=" + attachment);
ms.Close();
Any and all help is appreciated
In general, the mailto: URL scheme does not support attachments. Thus, you should not use it at all if you need it to work reliably with attachments.
Apparently, some mail clients still support passing Attachment=..., but they expect the ... part to be the path of a local file. Thus, in your case, you need to
save the file to disk (you can use a temporary file name in a temporary folder) and then
pass the path of the file to your mailto: link.
Note that you will have to keep the file around until the user has actually sent the mail, so you might have to think about "cleaning up" those temporary files at a later time.
Currently I just created a program which can send the .xls file, I used google smtp server so I can already sent email with that server in my program. And inside my program, based on my date, I can create .xls file with today date and time. What I want to know is this file will be used to be attachment for the email. How I can do it?
Currently my file name is DateTime.Now.ToString("yyyyMMdd_hhss") + ".xls" so I can create based on today date and time. How I can retrieve the file name to be used as email attachment?
I am assuming you are using the .NET's default Emal api found in the System.Net.Mail namespace.
You can add the file as an attachment to the System.Net.Mail.MailMessage object.
The System.Net.Mail.Attachment class accepts a constructor that can take the stream which you could have used to create the xls file. So, if you create the excel file on the fly, you'll probably already have a reference to the stream where it is written. Then you need to use the code below:
using (Stream myXlsFileStream = new MemoryStream())
{
// assumig you populate the stream like this...
WriteXlsToStream(myXlsFileStream);
myXlsFileStream.Flush();
MailMessage message = new MailMessage();
// configure mail message contents ...
using (Attachment xlsAttachment = new Attachment(
myXlsFileStream,
"FileNameToAppearInEmail.xls",
"application/xls"))
{
message.Attachments.Add(xlsAttachment);
// send the message
}
}
Please, note that the stream must not be closed, and all data should have been written to it. You may need to call myXlsFileStream.Flush() (as in above code) before adding the attachment, in order to ensure that the file is entirely written.