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.
Related
I am programmatically trying to get the attachment data in C# in following way :--
Microsoft.Office.Interop.Outlook.Attachment attachment = objMail.Attachments[attachmentIndex];
if (attachment.DisplayName.Equals("Test"))
{
const string PR_ATTACH_DATA = "http://schemas.microsoft.com/mapi/proptag/0x37010102";
byte[] attachmentData = attachment.PropertyAccessor.GetProperty(PR_ATTACH_DATA);
}
Now my code is working fine if attachment is text file or image file. But if attachment is itself a mail, it throws the exception that property is unknown or can not be found.
Please suggest in which cases / type of attachments, this property "http://schemas.microsoft.com/mapi/proptag/0x37010102" will not work and in those cases, what would be alternative property / method to get the attachment data in byte array ?
Thanks
PR_ATTACH_DATA_BIN is only present for the regular by-value attachments (PR_ATTACH_METHOD == ATTACH_BY_VALUE). Embedded message or OLE attachments do not expose that property - they use PR_ATTACH_DATA_OBJ which must be opened using IAttach::OpenProperty(IID_IStorage, ...) - take a look at the existing messages using OutlookSpy - I am its author - select the message, click IMessage button, go to GetAttachmentTable tab, double click on the attachment.
Also keep in mind that PropertyAccessor.GetProperty would only be able to retrieve that property for small attachments. For large attachments, PR_ATTACH_DATA_BIN must be opened as IStream using IAttach::OpenProperty(IID_IStorage, ...) - PropertyAccessor.GetProperty does not do that. You will need to use Extended MAPI (C++ or Delphi) or Redemption (I am its author) - it exposes RDOAttachment.AsArray / AsText / AsStream properties.
Microsoft Graph Rest API is an single end point and wrapper for most Microsoft Data including events, most office products including outlook. Best of all any language can make a request to the endpoint and retrieve the information. See the complete Docs HERE to get started.
See below code for a simple Get request for outlook attachments. Note there are other more complex implementations. Docs: https://learn.microsoft.com/en-us/graph/api/attachment-get?view=graph-rest-1.0&tabs=http Scroll through the link and you can find C#, Java, and JavaScript examples on how to implement this.
GET /me/messages/{id}/attachments/{id}
GET /users/{id | userPrincipalName}/messages/{id}/attachments/{id}
GET /me/messages/{id}/attachments/{id}/$value
GET /users/{id | userPrincipalName}/messages/{id}/attachments/{id}/$value
I am using IMAP4 client called: MailKit.
It works great, but I have a problem on getting body of message without downloading the attachments. I want to show the mail's body text and also what attachments there are,
but only if a user clicks on the attachment I want to actually download the attachment.
I've tried:
var message = inbox.GetMessage(uid, cancel.Token);
But this gets the entire message.
Also tried:
uids[0] = uid;
var ms = inbox.Fetch(uids, MessageSummaryItems.BodyStructure , cancel.Token);
var bp1 = inbox.GetBodyPart(uid, ms.First().Body, cancel.Token);
But again this downloads the attachment.
With your sample code, you are downloading the entire message because you are requesting the top-level body part of the message.
MIME is a tree structure of "body parts". What you want to do is to traverse the ms.First().Body to find the part(s) that you want, and then download them individually using the GetBodyPart() method.
Take a look at MailKit.BodyPartMultipart, MailKit.BodyPartMessage, MailKit.BodyPartBasic and MailKit.BodyPartText.
A BodyPartMultipart contains other body parts.
A BodyPartMessage parts contains a message (which will also contain a body part).
A BodyPartBasic is a basic leaf-node body part - usually an "attachment".
A BodyPartText is a text part (a subclass of BodyPartBasic) which can either be an attached text part or what you might consider to be the main text of the message.
To figure out if a BodyPartBasic is meant to be displayed inline or as an attachment, what you need to do is:
if (part.ContentDisposition != null && part.ContentDisposition.IsAttachment)
// it is an attachment
else
// it is meant to be shown to the user as part of the message
// (if it is an image, it is meant to be displayed with the text)
I should probably add a convenience property to BodyPartBasic called IsAttachment to make this a bit simpler (I'll try to add it today).
Hope that helps.
Update: I've just added the BodyPartBasic.IsAttachment convenience property in git master, so the next release of MailKit will have it.
This IMAP command will return just the text body.
a1 uid fetch <uid> (body.peek[text])
-Rick
I'm writing a program where I can send mails (using domino.dll) from three different department mailboxes (each using its own mail server and nsf-file).
All three department mailboxes have a predefined mail signature such as
Regards
Department X
Since those can change anytime I don't want to hardcode the signature in my program but extract them from the mailbox/nsf-file instead and append it to the mail body (or something else if there are better approaches).
I've been looking around all day without finding a solution to this problem, so my question is: How is this achieved?
So far my code is similar to this:
public Boolean sendNotesMail(String messageText)
{
//Create new notes session
NotesSession _notesSession = new NotesSession();
//Initialize Notes Database to null;
NotesDatabase _notesDataBase = null;
//Initialize Notes Document to null;
NotesDocument _notesDocument = null;
string mailServer = #"Path/DDB";
string mailFile = #"Deparmentmail\number.nsf";
//required for send, since its byRef and not byVal, gets set later.
object oItemValue = null;
// Start the connection to Notes. Otherwise log the error and return false
try
{
//Initialize Notes Session
_notesSession.Initialize("");
}
catch
{
//Log
}
// Set database from the mailServer and mailFile
_notesDataBase = _notesSession.GetDatabase(mailServer, mailFile, false);
//If the database is not already open then open it.
if (!_notesDataBase.IsOpen)
{
_notesDataBase.Open();
}
//Create the notes document
_notesDocument = _notesDataBase.CreateDocument();
//Set document type
_notesDocument.ReplaceItemValue("Form", "Memo");
//sent notes memo fields (To and Subject)
_notesDocument.ReplaceItemValue("SendTo", emailAddress);
_notesDocument.ReplaceItemValue("Subject", subjectText);
// Needed in order to send from a departmental mailbox
_notesDocument.ReplaceItemValue("Principal", _notesDataBase.Title);
//Set the body of the email. This allows you to use the appendtext
NotesRichTextItem _richTextItem = _notesDocument.CreateRichTextItem("Body");
// Insert the text to the body
_richTextItem.AppendText(messageText);
try
{
_notesDocument.Send(false, ref oItemValue);
}
}
EDIT:
Thanks to Richard Schwartz my solution is:
object signature = _notesDataBase.GetProfileDocument("calendarprofile", "").GetItemValue("Signature");
String[] stringArray = ((IEnumerable)signature).Cast<object>().Select(x => x.ToString()).ToArray();
_richTextItem.AppendText(stringArray[0]);
The signature is stored in a profile document in the NSF file. You can use the method NotesDatabase.getProfileDocument() to access it. This method takes two arguments:
ProfileName: The profile document name that you need to find the signature is "calendarprofile". (Yes, that's right. It's actually a common profile for many functions, but the calendar developers got there first and named it. ;-))
UniqueKey: Leave this as an empty string. (It is traditionally used to store a username in profile documents in shared databases, but not used in the calendarprofile doc in the mail file.)
You access data in the profile document the same way that you access them in regular documents, e.g., using getItem(), getItemValue(), etc. For a simple text signature, the NotesItem that you are looking for is called "Signature". I notice, however, that there are also items called "Signature_1" and "Signature_2", and "SignatureOption".
If you look at the Preferences UI for setting signatures in Notes mail, you will see that there is a choice between simple text and HTML or graphic files. No doubt this choice will be reflected in the SignatureOption item, so you will probably want to check that first. I have not explored where the data goes if you use imported HTML or graphic files, so I can't say for sure whether it goes into Signature, Signature_1, Signature_2, or somewhere else. But you can explore that on your own by using NotesPeek. You can download it here. It presents a tree-style view of the NSF file. There's a branch of the tree for Profiles, and you can find the calendarprofile there. Then just play around with different settings in the Notes mail preferences and see what changes. (NotesPeek doesn't pick up changes on the fly. You have to close and re-open the profile in NotesPeek after saving changes in the Notes mail preferences dialog in order to see the changes.)
If this gets too difficult, and you want a standard solution for all mails, you might consider this product or a similar one.
First, sorry for my English.
I would like do display a list of attachments names files on mailbox. Then from this list the users will can choose which attachments they want to download on hdd - for example such as in this program:
http://www.mailresender.com.ar/Read.html
I have already wrote this in Koolwired.Imap:
for (int i = 0; i < mbStructure.BodyParts.Count; i++) // i = number of parts of body
{
if (mbStructure.BodyParts[i].Attachment)
{
mbStructure = command.FetchBodyPart(mbStructure, i);
System.Console.WriteLine(mbStructure.BodyParts[i].Disposition); // Disposition contains file name
//System.Console.WriteLine(mbStructure.BodyParts[i].Data); // Data contains entire file
}
}
but the problem is that not only file name is loaded into memory but also data binary (entire attachment file is loaded by FetchBodyPart method) - so if files in attachment have long size (for instance 20 MB) all this 20 MB's also have to be loaded into memory, so displaying the list of file names will last very very long time.
Is there a way to load only attachments files names without files? Which library in c# could support this?
The answer to your question is ImapCommand.FetchBodyStructure, but as I'm browsing through the source I bet it will fail on some emails.
You may also take a look at Mail.dll IMAP component especially this blog post may be useful:
http://www.lesnikowski.com/blog/index.php/get-email-information-from-imap-fast/
Please note that Mail.dll is a commercial product I created.
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.