I have looked at other examples online, but I am unable to figure out how to download and store ALL the attachments from a MimeMessage object.
I did look into the WriteTo(), but I could not get it to work.
Also wondering whether attachments will be saved according to the original file name, and type inside the email.
Here is what I have so far:
using (var client = new ImapClient())
{
client.Connect(Constant.GoogleImapHost, Constant.ImapPort, SecureSocketOptions.SslOnConnect);
client.AuthenticationMechanisms.Remove(Constant.GoogleOAuth);
client.Authenticate(Constant.GoogleUserName, Constant.GenericPassword);
if (client.IsConnected == true)
{
FolderAccess inboxAccess = client.Inbox.Open(FolderAccess.ReadWrite);
IMailFolder inboxFolder = client.GetFolder(Constant.InboxFolder);
IList<UniqueId> uids = client.Inbox.Search(SearchQuery.All);
if (inboxFolder != null & inboxFolder.Unread > 0)
{
foreach (UniqueId msgId in uids)
{
MimeMessage message = inboxFolder.GetMessage(msgId);
foreach (MimeEntity attachment in message.Attachments)
{
//need to save all the attachments locally
}
}
}
}
}
This is all explained in the FAQ in the "How do I save attachments?" section.
Here is a fixed version of the code you posted in your question:
using (var client = new ImapClient ()) {
client.Connect (Constant.GoogleImapHost, Constant.ImapPort, SecureSocketOptions.SslOnConnect);
client.AuthenticationMechanisms.Remove (Constant.GoogleOAuth);
client.Authenticate (Constant.GoogleUserName, Constant.GenericPassword);
client.Inbox.Open (FolderAccess.ReadWrite);
IList<UniqueId> uids = client.Inbox.Search (SearchQuery.All);
foreach (UniqueId uid in uids) {
MimeMessage message = client.Inbox.GetMessage (uid);
foreach (MimeEntity attachment in message.Attachments) {
var fileName = attachment.ContentDisposition?.FileName ?? attachment.ContentType.Name;
using (var stream = File.Create (fileName)) {
if (attachment is MessagePart) {
var rfc822 = (MessagePart) attachment;
rfc822.Message.WriteTo (stream);
} else {
var part = (MimePart) attachment;
part.Content.DecodeTo (stream);
}
}
}
}
}
A few notes:
There's no need to check if client.IsConnected after authenticating. If it wasn't connected, it would have thrown an exception in the Authenticate() method. It would have thrown an exception in the Connect() method as well if it didn't succeed. There is no need to check the IsConnected state if you literally just called Connect() 2 lines up.
Why are you checking inboxFolder.Unread if you don't even use it anywhere? If you just want to download unread messages, change your search to be SearchQuery.NotSeen which will give you only the message UIDs that have not been read.
I removed your IMailFolder inboxFolder = client.GetFolder(Constant.InboxFolder); logic because you don't need it. If you are going to do the SEARCH using client.Inbox, then don't iterate over the results with a different folder object.
Related
I'm working writing a script to migrate emails from one account to another. I'm having two issues.
The messages are moving over, but the mail client is showing their
received date as the date/time it was moved (in the list of messages
view), not the date/time that's showing in the message header. I'm
guessing a file date isn't being retained?
The message flags aren't being copied over. Such as the message
being read already. I'd like to see all of the flags being passed...basically I need to move the message as it exists on the previous account.
protected void CopyBtn_Click(object sender, EventArgs e)
{
try
{
ImapClient Client = new ImapClient();
ImapClient Client2 = new ImapClient();
Client.Connect(SourceHostBox.Text.Trim(), 993, SecureSocketOptions.SslOnConnect);
Client.Authenticate(SourceUsernameBox.Text.Trim(), SourcePasswordBox.Text.Trim());
Client.Inbox.Open(FolderAccess.ReadWrite);
Client2.Connect(DestinationHostBox.Text.Trim(), 993, SecureSocketOptions.SslOnConnect);
Client2.Authenticate(DestinationUsernameBox.Text.Trim(), DestinationPasswordBox.Text.Trim());
Client2.Inbox.Open(FolderAccess.ReadWrite);
var folders = Client.GetFolders(Client.PersonalNamespaces[0]);
//move all messages in folders & create folders if necessary
foreach (var folder in folders)
{
folder.Open(FolderAccess.ReadWrite);
var uids = folder.Search(SearchQuery.All);
foreach (var uid in uids)
{
var folders2 = Client2.GetFolders(Client2.PersonalNamespaces[0]);
var message = folder.GetMessage(uid);
string currentFolder = folder.ToString().Replace("INBOX.", ""); //Remove the 'INBOX.' text that's getting prepended by cPanel/Dovecot
var toplevel = Client2.GetFolder(Client2.PersonalNamespaces[0]);
var folderExists = FindFolder(toplevel, currentFolder);
if (folderExists == null)
toplevel.Create(currentFolder, true);
Client2.GetFolder(currentFolder).Append(message);
}
}
//move inbox messages
Client.Inbox.Open(FolderAccess.ReadWrite);
Client2.Inbox.Open(FolderAccess.ReadWrite);
var inboxuids = Client.Inbox.Search(SearchQuery.All);
foreach (var uid in inboxuids)
{
var message = Client.Inbox.GetMessage(uid);
Client2.Inbox.Append(message);
}
label1.Text = "Finished Successfully.";
label1.ForeColor = System.Drawing.Color.Green;
}
catch (Exception ex)
{
label1.Text = ex.Message;
label1.ForeColor = System.Drawing.Color.Red;
}
}
You need to use one of the other Append() methods that takes a MessageFlags argument and a DateTimeOffset argument to specify the timestamp for when the message arrived.
But in order to get that information, you will also need to Fetch() that metadata for each of the messages.
Here's how I would change your loop:
var inboxuids = Client.Inbox.Search(SearchQuery.All);
foreach (var uid in inboxuids)
{
var message = Client.Inbox.GetMessage(uid);
Client2.Inbox.Append(message);
}
Fixed:
var uids = Client.Inbox.Search (SearchQuery.All);
var items = Client.Inbox.Fetch (uids, MessageSummaryItems.InternalDate | MessageSummaryItems.Flags);
foreach (var item in items)
{
var message = Client.Inbox.GetMessage (item.UniqueId);
Client2.Inbox.Append (message, item.Flags.Value, item.InternalDate.Value);
}
Like the OP, I am having the same issue. I used the solution provided by #jstedfast but the destination account still shows the copied email having the date and time of the copy and not the original date and time. Below is my code. Any suggestions would be greatly appreciated!
Edit (12/21/2021):
I figured out the cause of this issue! I had initially tried copying over a bunch of emails without using the flags that retains the date and time. I didn't know that would be required. So I then implemented the changes necessary to use those flags and before running the program again, I deleted all of the original emails that I had copied over from the previous version of my program, but I never emptied the trash in gmail. So although I was copying over the messages again, it must have just used the original copied messages and just removed the deleted flag. Once I emptied the gmail trash and ran my program again, the emails copied over correctly while retaining the original received date.
private void CopyEmailsAndFolders()
{
try
{
ImapClient yahooEmailAccnt = connectToEmailServerAndAccount(SrcEmailAccount.ImapServer, SrcEmailAccount.LogFileName, SrcEmailAccount.AccountUserName, SrcEmailAccount.AccountPassword);
ImapClient gmailEmailAccnt = connectToEmailServerAndAccount(DestEmailAccount.ImapServer, DestEmailAccount.LogFileName, DestEmailAccount.AccountUserName, DestEmailAccount.AccountPassword);
try
{
List<string> yahooFoldersList = getFoldersList(yahooEmailAccnt);
//loop through each of the folders in the source (Yahoo) email account
foreach (string folderName in yahooFoldersList)
{
if (folderName.ToLower() == "inbox")
{
continue;
}
var gmailFolder = gmailEmailAccnt.GetFolder(folderName);
var yahooFolder = yahooEmailAccnt.GetFolder(folderName);
yahooFolder.Open(FolderAccess.ReadOnly);
//get a list of all email UID's
var uids = yahooFolder.Search(SearchQuery.All);
//now get a list of all the items in the folder to include the critical date time properties we want to preserve
var items = yahooFolder.Fetch(uids, MessageSummaryItems.InternalDate | MessageSummaryItems.Flags);
//now loop through all of the emails we found in the current folder and copy them to the destination (gmail) email account
foreach (var item in items)
{
var message = yahooFolder.GetMessage(item.UniqueId);
gmailFolder.Append(message, item.Flags.Value, item.InternalDate.Value);
}
}
}
catch (Exception fEx)
{
MessageBox.Show(fEx.Message);
}
finally
{
yahooEmailAccnt.Disconnect(true);
gmailEmailAccnt.Disconnect(true);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private ImapClient connectToEmailServerAndAccount(string imapServer, string logFile, string userName, string password)
{
ImapClient newEmailClient = new ImapClient();
try
{
newEmailClient = new ImapClient(new ProtocolLogger(logFile));
newEmailClient.Connect(imapServer, 993, SecureSocketOptions.SslOnConnect);
newEmailClient.Authenticate(userName, password);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
return newEmailClient;
}
private List<string> getFoldersList(ImapClient emailClient)
{
List<string> foldersList = new List<string>();
try
{
// Get the first personal namespace and list the toplevel folders under it.
var personal = emailClient.GetFolder(emailClient.PersonalNamespaces[0]);
foreach (var folder in personal.GetSubfolders(false))
{
foldersList.Add(folder.Name);
Console.WriteLine("[folder] {0}", folder.Name);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
return foldersList;
}
I am using bot framework (C# SDK) for my bot in skype. It works fine in the emulator but when i run it on the skype, it does not display image attachemnts.
I have checked documentation but i couldn't find any solution. I am not sure if it is the problem on my end or in the skype.
Note: I am using skype for windows 10 and skype for android on my cell phone (does not work in either of these)
Here is http response for attachement in emulator
Here is the image in skype
Here is the method that generates attachement
public Attachment ChartCard(StandardInfoData[] data)
{
if (data != null && data.Length != 0)
{
string chartUrl = data[0]?.report?.url;
Attachment attachment = new Attachment();
attachment.ContentUrl = chartUrl;
attachment.ContentType = "image/png";
attachment.Name = "Chart Report";
return attachment;
}
else
{
Attachment attachment = new Attachment();
attachment.Content = "No Chart Report Found";
return attachment;
}
}
Here is the code that calls this method. I am getting this image from our server "Human Collaborator" and using sockets to communicate with it.
For sockets i am using "websocket-sharp" library
private async Task StandardInfoFormComplete(IDialogContext context, IAwaitable<StandardInfoForm> result)
{
try
{
var form = await result;
eventOccured = false;
SocketStream.ws.OnMessage += (sender, e) =>
{
try
{
var json = e.Data;
StandardInfoJson response = JsonConvert.DeserializeObject<StandardInfoJson>(json);
var data = response.data;
if (data[0].result.ToLower() == "ok")
{
// For chart reports
var chartAttachment = card.ChartCard(data);
replyToConversation.Attachments.Add(chartAttachment);
context.PostAsync(replyToConversation);
}
if (data[0].result.ToLower() == "error")
{
var replyToConversation = context.MakeMessage();
var message = data[0]?.message;
replyToConversation.Text = message;
context.PostAsync(replyToConversation);
}
}
catch (NullReferenceException ex)
{
Debug.WriteLine(ex.StackTrace);
}
eventOccured = true;
};
//builds the botRequest part of json string
Utilities.StringBuilder.JsonStringBuilder(context, result);
// Instantiate JSONDataObjects class
JSONDataObjects jdo = new JSONDataObjects();
//Create JSON string
string output = JsonConvert.SerializeObject(jdo, Formatting.Indented);
Debug.WriteLine("USER: \n" + output);
//Sending the Json object to Human-Collaborator
SocketStream.ws.Send(output);
await context.PostAsync("Generating response.....Please Be Patient.....");
Thread.Sleep(8000);
if (!eventOccured)
await context.PostAsync("Server is busy ... Please try again later");
context.Done(true);
}
catch (Exception e)
{
string reply;
if (e.InnerException == null)
{
reply = $"You quit --maybe you can finish next time!";
}
else
{
reply = "Sorry, I've had a short circuit. Please try again.";
}
await context.PostAsync(reply);
}
}
Please help me what i am doing wrong. Thanks
I don't have all the code to replay your case but I see one important thing:
else
{
Attachment attachment = new Attachment();
attachment.Content = "No Chart Report Found";
return attachment;
}
Here you should not create an attachment as you have nothing to attach. This will throw an error because the ContentType is missing.
Can you modify the code that is calling your ChartCard: it should not call it when its parameter (your StandardInfoData[]) is empty.
I am not able to test more with the code you provided, but you should also check if your data in the Socket call is okay when calling from Skype (like by outputting the value or any other way), it will help in your investigation
EDIT: after a lot of comments on this answer, looks like the problem comes from the fact that the image is not reachable from Skype (located on a server inside user's network)
The title sums it up. I need to get all the MessageId properties from an Imap folder without downloading the entire message.
...
IMailFolder inbox = imapClient.Inbox;
SearchQuery query = SearchQuery.All;
IList<UniqueId> idsList = inbox.Search(query);
foreach (var uid in idsList)
{
MimeMessage message = inbox.GetMessage(uid);//This gets the entire message
//but I only need the .MessageId, without getting the other parts
if (message != null)
{
string messageId = message.MessageId;
}
}
...
Try this instead:
var summaries = client.Inbox.Fetch (0, -1, MessageSummaryItems.Envelope);
foreach (var message in summaries) {
Console.WriteLine (message.Envelope.MessageId);
}
That should get you what you want.
I have a method which accepts a ExchangeWebServices.MessageType parameter. I am using this variable to get the attachments from the message. When I get the contents of the attachments, it is always null. But file name is being read correctly. Below is the code I am using:
public void StripAttachments(MessageType fullMessage)
{
AttachmentType[] attachments = fullMessage.Attachments;
if (attachments != null && attachments.Length > 0)
{
foreach (AttachmentType attachment in attachments)
{
if (attachment is FileAttachmentType)
{
FileAttachmentType file = (FileAttachmentType)attachment;
byte[] contents = file.Content; //Always null
try
{
if(contents != null)
{
System.IO.File.WriteAllBytes(#"C:\TestLocation" + file.Name, contents);
}
}
catch (Exception ex)
{
}
}
}
}
}
Is there a better method to get the attachments from a specific message?
Try using EmailMessage instead of MessageType
You will need to use EmailMessage.Bind(ExchangeService, ItemId) to populate the list of attachments and work with them, otherwise an exception will be thrown.
public void StripAttachments(ItemId id)
{
EmailMessage email = EmailMessage.Bind(service, id)
foreach (Attachment a in email.Attachments)
{
if (a is FileAttachment)
{
// do your thing
}
}
}
Also check out Getting attachments by using the EWS Managed API for more of an idea.
Here I am trying to get all the part of an email separately like body, attachments, address part also So I have the below code . So can I get the attachment files also by using same technique( Using PropertySet class or define RequestedBodyType to something)???
Is there any way to get the contents of Attachment files of any type and I don't need to change the code too much??
// Get the Unread mails from the server
SearchFilter itemFilter = new SearchFilter.IsEqualTo(EmailMessageSchema.IsRead, false);
// get the emails from Inbox folder
FindItemsResults<Item> emails = service.FindItems(WellKnownFolderName.Inbox, itemFilter, view);
foreach (EmailMessage em in emails)
{
itempropertyset.RequestedBodyType = BodyType.HTML;
em.Load(itempropertyset);
em.IsRead = true;
em.Update(ConflictResolutionMode.AlwaysOverwrite);
EmailProList.HTMLBody = em.Body.Text;
itempropertyset.RequestedBodyType = BodyType.Text;
em.Load(itempropertyset);
EmailProList.Body = em.Body.Text;
itempropertyset.RequestedBodyType =
EmailProList.ToEmailAddr = em.Sender.Address.ToString(); //JG Changed
EmailProList.Subject = em.Subject.ToString();
EmailProList.Type = "Feedback";
}
You can get the attachments like this:
EmailMessage email = item as EmailMessage;
foreach(FileAttachment file in email.FileAttachments)
{
// Process the attachment
}
You can process the attachments by this way.
if (message.HasAttachments && message.Attachments[0] is FileAttachment)
{
FileAttachment fileAttachment = message.Attachments[0] as FileAttachment;
Console.WriteLine("email" + fileAttachment.Name);
fileAttachment.Load(#"D:\\" + fileAttachment.Name);
}