I'm looping sent item in EWS, and try to show details of each sent emails, receiver, subject, body etc. However, I found receiver in the sent email message is null. How to get receiver email address?
My code:
ItemId id = (ItemId)Request["id"]; // this id is the item id of WellKnownFolderName.**SentItems**
EmailMessage current = EmailMessage.Bind(service, id);
La_Subject.Text = current.Subject;
La_From.Text = current.Sender.ToString();
La_Sent.Text = current.DateTimeReceived.ToString();
La_To.Text = current.ReceivedBy.ToString(); // This line error occurs
Any idea?
To get the recipients of a mail, use the DisplayTo and DisplayCC properties of the mail message.
Or iterate through the ToRecipients collection yourself and build the string yourself:
var toRecipients = string.Join(", ",
mail.ToRecipients.Select(
address => string.Format("\"{0}\" <{1}", address.Name, address.Address)));
The ReceivedBy property is used in delegate scenarios. See http://msdn.microsoft.com/en-us/library/microsoft.exchange.webservices.data.emailmessage.receivedby(v=exchg.80).aspx.
Related
I'm working on an issue in our code base where we want to extract all recipients of an email message received by Outlook. I understand that if recipients are on the BCC line, the receivers of that email don't know about those recipients, but if you're on the BCC line, you do know (it's in the email headers). So I have a message that looks like this:
From: FirstName LastName<name1#host1.tld>
Date: Thu, 16 Jul 2020 09:48:10 +0100
Message-ID: <CAE+EE4GS7RtDKgPGOUbTFQ3=7i9+QiKB++1cx7qqLd_09PRZFg#mail.gmail.com>
Subject: Testing
To: undisclosed-recipients:;
Bcc: name2#host2.tld
Content-Type: multipart/alternative;
Using the Redemption RDOMail.Recipients, I would expect that it would have one item and that it would be name2#host2.tld (since that's all the information I have about recipients of this email). When I actually loop through the recipients, I get a count of 0. Shouldn't it return any BCC addresses it does know about?
Example Code (might be some syntax errors as I'm simplyfing live code):
public IEnumerable<string> GetRecipients(RDOMail mailItem)
{
var recipients = mailItem.Recipients;
for (var i = 0; i < recipients.Count; i++) // never enters as Count == 0
{
var recipient = recipients[i];
var smtpAddress = GetSmtpAddress(recipient);
yield return smtpAddress;
}
}
There is a BCC property of RDOMail, but it is also blank for received mail. The documentation does note: This property contains the display names only. The Recipients collection should be used to modify the BCC recipients. Which leads me to believe that I can use the Recipients collection for Read operations as well.
Yes, I realize I might be able to assume that if the current address isn't on the TO or CC lines, it's on the BCC line. But this doesn't work for shared mailboxes... or rather, I'd have to do a lot of digging to understand where the received mail item is coming from in order to make the correct assumption.
Updated with Solution
I ended up working with Eugene's answer and combined it with looking at the SMTP BCC header for received email as well. There are still some cases (internal email where you're on the BCC header might not contain SMTP headers at all in an On-Prem Exchange Environment), but at this point, I think it's pretty complete. So I still use the above code for GetRecipients(RDOMail mailItem), but after I do that, I also call:
Some Constants
public const string PR_TRANSPORT_MESSAGE_HEADERS = "http://schemas.microsoft.com/mapi/proptag/0x007D001F"
public const string PID_TAG_RECEIVED_REPRESENTING_SMTP_ADDRESS = "http://schemas.microsoft.com/mapi/proptag/0x5D08001F"
Test if the receiver is in the BCC
private bool IsReceiverInBcc(RDOMail mailItem)
{
if (mailItem == null) return false;
var receiver = ConvertReceiverToContact(mailItem);
var messageHeaders = mailItem.Fields(Consts(PR_TRANSPORT_MESSAGE_HEADERS)?.ToString();
if (!string.IsNullOrWhiteSpace(messageHeaders)
{
var bccHeaderRegex = new Regex($"{Environment.NewLine}Bcc: .*{Environment.NewLine}", RegexOptions.IgnoreCase);
foreach (Match headerMatch in bccHeaderRegex.Matches(messageHeaders))
{
if (headerMatch?.Value?.IndexOf(receiver.address, CompareOptions.IgnoreCase) >= 0
{
return true;
}
}
}
return false;
}
Extract the current user from the BCC header
private EmailContact ConvertReceiverToContact(RDOMail mailItem)
{
var contact = new EmailContact
{
Address = mailItem.Fields(Consts.PID_TAG_RECEIVED_REPRESENTING_SMTP_ADDRESS),
Name = mailItem.ReceivedByName
};
return contact;
}
The Outlook object model (as well as Extended MAPI) doesn't provide any information in the Recipients collection. The Count property returns 0 in this scenario. In the case of Extended MAPI the IMessage::GetRecipientTable method returns an empty table. Here is what MSDN states:
The IMessage::GetRecipientTable method returns a pointer to the message's recipient table, which includes information about all of the recipients for the message. There is one row for every recipient.
So, there are no issues in the Redemption library.
Based on my research performed with a sample email message recieved with the following content in the internet header:
To: undisclosed-recipients:;
Bcc: eugene#somedomain.com
In the case of OOM, you can use the MailItem.ReceivedByName property which returns a string representing the display name of the true recipient for the mail message.
In the case of Extended MAPI (Redemption is a wrapper around this API), you can use the PidTagReceivedRepresentingSmtpAddress property which contains the SMTP email address of the user represented by the receiving mailbox owner. The DASL property name is http://schemas.microsoft.com/mapi/proptag/0x5D08001F.
No, even BCC recipients won't be in the BCC header. You can generate the BCC header when saving in the MIME format, but it won't be present in messages received from a POP3 or IMAP4 server.
Generally, when a message is sent through SMTP, the server does not look at the MIME headers to figure out the recipients. The recipients are determined by the RCPT TO SMTP command. And no email client specifies BCC header in the outgoing messages - that would be a bug.
I'm using MailKit to send emails for account confirmation and password reset. These operations work as intended, but now I'm trying to modify the same code to also send email notifications to all registered users but this is where I'm having issues.
I've built a List collection using the following code:
public List<string> UserList {
get {
var allUsers = userManager.Users;
return allUsers.Select(x => x.Email).ToList();
}
}
this collection returns all emails within my AspNetUsers table successfully.
Next I create 3 variables:
var toAddress = UserList;
var subject = "New Asset Added" + " " + model.Name;
var body = model.Name + model.AssetType + model.AssetURL;
When debugging and stepping through the code I do see Count = 2 and then the list of emails in the variable. So far this looks good
After the 3 variables I have this line of code that passes the collected information to the Send method:
_emailSender.Send(toAddress[0], subject, body);
Again, the toAddress[0] when stepping through code shows Count = 2 so I'm assuming the way I have it written is valid.
Once I reach the Send Method code and I check the toAddress variable again, it is now stripped of the Count = 2 and becomes just the first email address in the list which makes sense since I passed the variable with an index of 0. What I really need is to pass all emails not just 1.
If I remove the brackets or leave the brackets empty I get an error:
cannot convert from 'System.Collections.Generic.List<string>' to 'string'
So for now I'm forced to add an index.
My send method seems fairly simple but I do believe my foreach loop might also not be well formulated. Here's my Send Method in it's entirety.
public async void Send(string toAddress, string subject, string body, bool sendAsync = true) {
var mimeMessage = new MimeMessage(); // MIME : Multipurpose Internet Mail Extension
mimeMessage.From.Add(new MailboxAddress(_fromAddressTitle, _fromAddress));
foreach (char toAddresses in toAddress) {
mimeMessage.To.Add(new MailboxAddress(toAddresses)); //I get error saying cannot convert char to string.
}
mimeMessage.Subject = subject;
var bodyBuilder = new MimeKit.BodyBuilder {
HtmlBody = body
};
mimeMessage.Body = bodyBuilder.ToMessageBody();
using (var client = new MailKit.Net.Smtp.SmtpClient()) {
client.Connect(_smtpServer, _smtpPort, _enableSsl);
client.Authenticate(_username, _password); // If using GMail this requires turning on LessSecureApps : https://myaccount.google.com/lesssecureapps
if (sendAsync) {
await client.SendAsync(mimeMessage);
}
else {
client.Send(mimeMessage);
}
client.Disconnect(true);
}
}
My end goal is to use a foreach loop on the mimeMessage.To.Add(new MailboxAddress(toAddresses)); so that everyone can receive an email if there is more than 1 email address, if not it can loop once.
Thanks in advance!
In your method, Send(), you are taking in a string for toAddress and then splitting its characters... that wont work. If you want to to use one or more email addresses, you will need to send in email address(es) separated by a delimiter or as a complete list.
Two ways to go about it.
Change the argument from string to List
change your method to take in a List
public async void Send(List<string> toAddress, string subject, string body, bool sendAsync = true)
{
var mimeMessage = new MimeMessage(); // MIME : Multipurpose Internet Mail Extension
mimeMessage.From.Add(new MailboxAddress(_fromAddressTitle, _fromAddress));
toAddress.ForEach(address => mimeMessage.To.Add(new MailboxAddress(address)));
...
if you use this method, you will need to send in the list as well.. not a single email address string.
List<string> toAddress = new List<string>() { "firstEmail", "Second" ...};
_emailSender.Send(toAddress, subject, body);
// You will not be able to do send in single string
_emailSender.Send("firstemail#only.com", subject, body); //Exception no method found.
Send in a delimited list
Always send in comma separated email addresses and translate those to multiple addresses when adding them to To field of your mimeMessage.
public async void Send(string toAddress, string subject, string body, bool sendAsync = true)
{
var mimeMessage = new MimeMessage(); // MIME : Multipurpose Internet Mail Extension
mimeMessage.From.Add(new MailboxAddress(_fromAddressTitle, _fromAddress));
// HERE -> FOREACH string, not char.
foreach (string toAddresses in toAddress.Split(',')) // Split on ,
{
mimeMessage.To.Add(new MailboxAddress(toAddresses));
}
and you will need to use this method the following way...
List<string> toAddress = new List<string>() {"first", "..."};
_emailSender.Send(string.Join(',',toAddress), subject, body);
I have an application that has been working fine for several weeks, basically it polls an Exchange 2010 mail server mailbox for new messages, gets the details of each one and any attachments, and use that information to create cases (with attachments) on the SalesForce CRM.
After each case is created from the email, the email must be marked as read by retrieving it from Exchange using the stored Item ID and calling the property set.
For several weeks this has been working fine, even getting some extra properties from Exchange such as the Subject, From, To properties which I can use in the session log.
Suddenly today the mail isn't being returned by Exchange from the itemID if I try to include the PropSet - if I omit the PropSet it works fine.
here is the code:
try
{
//creates an object that will represent the desired mailbox
Mailbox mb = new Mailbox(common.strInboxURL); // new Mailbox(targetEmailAddress); #"bbtest#bocuk.local"
//creates a folder object that will point to inbox fold
FolderId fid = new FolderId(WellKnownFolderName.Inbox, mb);
//this will bind the mailbox you're looking for using your service instance
Microsoft.Exchange.WebServices.Data.Folder inbox = Microsoft.Exchange.WebServices.Data.Folder.Bind(service, fid);
// As a best practice, limit the properties returned to only those that are required.
PropertySet propSet = new PropertySet(BasePropertySet.IdOnly,
ItemSchema.Subject, EmailMessageSchema.IsRead, EmailMessageSchema.Sender, EmailMessageSchema.DateTimeReceived);
// Bind to the existing item by using the ItemId.
// This method call results in a GetItem call to EWS.
EmailMessage mail = EmailMessage.Bind(service, itemId); //, propSet);
if (!mail.IsRead) // check that you don't update and create unneeded traffic
{
mail.IsRead = true; // mark as read
mail.Update(ConflictResolutionMode.AutoResolve); // persist changes
}
mail.Update(ConflictResolutionMode.AlwaysOverwrite);
e2cSessionLog("\tcommon.MarkAsRead", "email Item ID: " + itemId);
//e2cSessionLog("\tcommon.MarkAsRead", "MARKED AS READ email Subject: " + mail.Subject.ToString() + "; From: " + mail.Sender.Name, mail.DateTimeReceived.ToString());
return true;
}
catch (Exception ex)
{
string innerException = "";
...
notice that in the line EmailMessage mail = EmailMessage.Bind(service, itemId); //, propSet); I have now omitted the PropSet argument and it now works.
But why?
Is there something else I need to do in order to always get the properties back with the mail returned using Item ID?
Any thoughts?
How read last unread message from mail box and after mark this message "Unseen"
I use s22.imap.dll
ImapClient Client = new ImapClient("imap.gmail.com", 993, "My_Username",
"My_Password", true, AuthMethod.Login);
// Get a list of unique identifiers (UIDs) of all unread messages in the mailbox.
uint[] uids = Client.Search( SearchCondition.Unseen() );
// Fetch the messages and print out their subject lines.
foreach(uint uid in uids) {
MailMessage message = Client.GetMessage(uid);
Console.WriteLine(message.Subject);
}
// Free up any resources associated with this instance.
Client.Dispose();
First get uid last unread message:
var lastUid = Client.Search( SearchCondition.Unseen().Last() );
and read this message;
MailMessage message = Client.GetMessage( lastUid );
To mark this message as "Unseen":
Client.RemoveMessageFlags( lastUid, null, MessageFlag.Seen );
See more on: ImapClient.RemoveMessageFlags Method
I'm trying to obtain some information from emails sent to an Outlook email. I've successfully connected to the Exchange Server and have been able to retrieve some information from emails with attachments (I am skipping emails without attachments).
What I Have: I can retrieve the attachment file name, the email date, and the email subject.
What I Need: I need to retrieve the sender name and email also. From what I've done, I can retreive the body of the email (in HTML), but not the body text only (requires Exchange 2013 - Hello MS advertising).
I'm new to C# and today is my first time to connect to the Exchange Server. I noticed from reading around that "find" is limited in what it can obtain, and that I'll need to bind the message in order to get more information from the email.
Code thus far:
foreach (Item item in findResults.Items)
if (item.HasAttachments) // && item.Attachments[0] is FileAttachment)
{
item.Load();
FileAttachment fileAttachment = item.Attachments[0] as FileAttachment;
date = Convert.ToString(item.DateTimeCreated);
name = Convert.ToString(fileAttachment.Name);
fileAttachment.Load("C:\\test\\" + fileAttachment.Name);
Console.WriteLine(name);
Console.WriteLine(item.Subject);
Console.WriteLine(date);
}
My question from here is if I do EmailMessage msg = EmailMessage.Bind ... what information will I need in order to grab more information?
Solved - for getting sender email and name as well as loading an attachment.
I used the EmailMessage class (just added it in the above loop, and added the variables to the beginning):
EmailMessage msg = (EmailMessage)item;
senderemail = Convert.ToString(msg.Sender.Address);
sendername = Convert.ToString(msg.Sender.Name);
I can then reproduce these on the console:
Console.WriteLine(senderemail);
Console.WriteLine(sendername);
Also, for loading an email's attaachment, I declared a byte[] variable at the beginning, loaded the attachment, converted it, and wrote its content to the console:
fileAttachment.Load();
filecontent = fileAttachment.Content;
System.Text.Encoding enc = System.Text.Encoding.ASCII;
string strFileContent = enc.GetString(filecontent);
Console.WriteLine(strFileContent);