I want to send EMails with C# and the Microsoft.Office.Interop.Outlook library.
I am locked in, on my Outlook account with a#domain.com and got the rights on the exchange server to send mails from b#domain.com and c#domain.com.
I don't find any way to send a mail with B or C.
I got everything working for my user account which has the mail a#domain.com.
Now I can't find a way to get the account of b#domain.com and send a mail in the name of B.
My Question:
How do I get access to the other accounts?
That's the code I have:
using Outlook = Microsoft.Office.Interop.Outlook;
public static Outlook.Account GetAccountForEmailAddress(Outlook.Application application, string smtpAddress)
{
// Loop over the Accounts collection of the current Outlook session.
Outlook.Accounts accounts = application.Session.Accounts;
if (_IsDebug)
Console.WriteLine($"Anzahl Accounts: {accounts.Count}");
foreach (Outlook.Account account in accounts)
{
// When the e-mail address matches, return the account.
if (_IsDebug)
Console.WriteLine($"Account: {account.SmtpAddress}");
if (String.Compare(account.SmtpAddress, smtpAddress, true) == 0)
{
return account;
}
}
throw new System.Exception(string.Format("No Account with SmtpAddress: {0} exists!", smtpAddress));
}
static void SendEMail(string emailadress)
{
try
{
var outlookApplication = new Outlook.Application();
var outlookMailItem = (Outlook.MailItem)outlookApplication.CreateItem(Outlook.OlItemType.olMailItem);
outlookMailItem.SendUsingAccount = GetAccountForEmailAddress(outlookApplication, ConfigurationManager.AppSettings[SENDER]);
if (_IsDebug)
Console.WriteLine($"Absender: {outlookMailItem?.SendUsingAccount?.SmtpAddress}");
outlookMailItem.HTMLBody = ConfigurationManager.AppSettings[BODY];
if (_IsDebug)
Console.WriteLine($"Body: {outlookMailItem?.HTMLBody}");
var file = GetPDFFile();
if (_IsDebug)
Console.WriteLine($"File: {file?.Name}");
if (file == null)
{
Console.WriteLine("Keine Datei gefunden!");
return;
}
string attachementDisplayName = file.Name;
int attachementPosition = outlookMailItem.HTMLBody.Length + 1;
int attachementType = (int)Outlook.OlAttachmentType.olByValue;
if (_IsDebug)
Console.WriteLine($"Dateianhang: {file.FullName}");
Outlook.Attachment outlookAttachement = outlookMailItem.Attachments.Add(file.FullName, attachementType, attachementPosition, attachementDisplayName);
outlookMailItem.Subject = ConfigurationManager.AppSettings[SUBJECT];
Outlook.Recipients outlookRecipients = outlookMailItem.Recipients;
Outlook.Recipient outlookRecipient = outlookRecipients.Add(emailadress);
outlookRecipient.Resolve();
outlookMailItem.Send();
outlookRecipient = null;
outlookRecipients = null;
outlookMailItem = null;
outlookApplication = null;
if (_IsDebug)
Console.ReadLine();
}
catch (Exception)
{
throw;
}
}
You have to grant the owner of the sending mailbox Send As permissions on the mailboxes you want to send the emails from. Send on behalf of permissions will work but will say "A on behalf of B" as the sender.
Related
We are working to integrate ASP.NET MVC web application with Azure PaaS. We are trying to get the User AD group from Azure Active directory but we have received the "Authorization Required" error.
DLL used: Microsoft.Azure.ActiveDirectory.GraphClient
Code used:
public async Task GetGroups(string objectId) {
IList groupMembership = new List();
try
{
if (objectId != null)
{
ActiveDirectoryClient client = AuthenticationHelper.GetActiveDirectoryClient();
IUser user = await client.Users.GetByObjectId(objectId).ExecuteAsync();
var userFetcher = (IUserFetcher)user;
IPagedCollection<IDirectoryObject> pagedCollection = await userFetcher.MemberOf.ExecuteAsync();
List<string> groupsname = new List<string>();
do
{
List<IDirectoryObject> directoryObjects = pagedCollection.CurrentPage.ToList();
foreach (IDirectoryObject directoryObject in directoryObjects)
{
if (directoryObject is Group)
{
var group = directoryObject as Group;
groupMembership.Add(group);
groupsname.Add(group.DisplayName);
}
}
pagedCollection = await pagedCollection.GetNextPageAsync();
} while (pagedCollection != null);
string groupscsv = string.Join(",", groupsname.ToArray());
System.Web.HttpContext.Current.Session["Groups"] = groupscsv;
Response.Redirect("~/Index.aspx");
}
}
catch (Exception e)
{
if (Request.QueryString["reauth"] == "True")
{
//
// Send an OpenID Connect sign-in request to get a new set of tokens.
// If the user still has a valid session with Azure AD, they will not be prompted for their credentials.
// The OpenID Connect middleware will return to this controller after the sign-in response has been handled.
//
HttpContext.GetOwinContext()
.Authentication.Challenge(OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
//
// The user needs to re-authorize. Show them a message to that effect.
//
ViewBag.ErrorMessage = "AuthorizationRequired";
return View();
}
Kindly help us to fix this issue or provide any other sample source to get the user AD groups from Azure Active Directory.
I have been working on a program that scans an exchange inbox for specific emails from a specified address. Currently the program reads the inbox, downloads the attachment, and moves the email to another folder. However, after about 15 pulls from the EWS server, the connection starts giving a 401 Unauthorized error until I restart the program. The program is setup to login via OAuth as basic auth is disabled by the system administrator. Below is the code that I am using to obtain the exchange connection and read the emails from the inbox.
Exchange Connection Code:
public static async Task<ExchangeService> GetExchangeConnection()
{
var pcaOptions = new PublicClientApplicationOptions
{
ClientId = AppID,
TenantId = TenantID,
};
var pca = PublicClientApplicationBuilder.CreateWithApplicationOptions(pcaOptions).Build();
var ewsScopes = new string[] { "https://outlook.office365.com/EWS.AccessAsUser.All" };
var securePassword = new SecureString();
foreach (char c in Pasword)
securePassword.AppendChar(c);
try
{
var authResult = await pca.AcquireTokenByUsernamePassword(ewsScopes, Username, securePassword).ExecuteAsync();
ExchangeService exchangeService = new ExchangeService()
{
Credentials = new OAuthCredentials(authResult.AccessToken),
Url = new Uri("https://outlook.office365.com/ews/exchange.asmx"),
};
return exchangeService;
}
catch
{
return null;
}
}
Email Retriever
public static List<Email> RetreiveEmails()
{
ExchangeService exchangeConnection = GetExchangeConnection().Result;
try
{
List<Email> Emails = new List<Email>();
TimeSpan ts = new TimeSpan(0, -5, 0, 0);
DateTime date = DateTime.Now.Add(ts);
SearchFilter.IsGreaterThanOrEqualTo EmailTimeFilter = new SearchFilter.IsGreaterThanOrEqualTo(ItemSchema.DateTimeReceived, date);
if (exchangeConnection != null)
{
FindItemsResults<Item> findResults = exchangeConnection.FindItems(WellKnownFolderName.Inbox, EmailTimeFilter, new ItemView(10));
foreach (Item item in findResults)
{
if (item.Subject != null)
{
EmailMessage message = EmailMessage.Bind(exchangeConnection, item.Id);
message.Load(new PropertySet(BasePropertySet.FirstClassProperties, ItemSchema.TextBody));
Emails.Add(new Email(message.DateTimeReceived, message.From.Name.ToString(), message.Subject, message.TextBody.ToString(), (message.HasAttachments) ? "Yes" : "No", message.Id.ToString()));
}
}
}
exchangeConnection = null;
return Emails;
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
return null;
}
}
The error occurs when the email retriever tries to either create the exchange connection or when requesting the emails from the folder. In either case the code will error out and give me 401 unauthorized while using credentials that work for the first dozen times and then fails after so many attempts. I have tried it with multiple different accounts and the issue persists with all of them and I have made sure that the application is authorized to access the exchange inbox. Any suggestions or help is much appreciated.
After doing further tracing regarding the 401 error it resulted in an issue with the token reaching the end of it's 1 hour lifespan. This is due to the original OAuth token having an initial life of 1 hour. This however was able to be fixed by setting up code to automatically refresh the token when needed. Here is the code to address this issue for anyone else who comes across this problem.
Authentication Manager:
class AuthenticationManager
{
protected IPublicClientApplication App { get; set; }
public AuthenticationManager(IPublicClientApplication app)
{
App = app;
}
public async Task<AuthenticationResult> AcquireATokenFromCacheOrUsernamePasswordAsync(IEnumerable<String> scopes, string username, SecureString password)
{
AuthenticationResult result = null;
var accounts = await App.GetAccountsAsync();
if (accounts.Any())
{
try
{
result = await (App as PublicClientApplication).AcquireTokenSilent(scopes, accounts.FirstOrDefault()).ExecuteAsync();
}
catch (MsalUiRequiredException)
{ }
}
if (result == null)
{
result = await (App as PublicClientApplication).AcquireTokenByUsernamePassword(scopes, username, password).ExecuteAsync();
}
return result;
}
}
I am using direct username and password authentication but the line of code can be switched to getting the user authentication via interactive methods as well. The code essentially creates a new instance of the authentication manager with a PublicClientApplication used to initialize it which houses the appID and tenantID. After initializing, you can call the AquireATokenFromCacheOrUsernamePasswordAsync which will attempt to see if there is an account present to get a token against. Next it will attempt to retrieve the previously cached token or refresh the token if it expires in less than 5 minutes. If there is a token available it will return that to the main application. If there isn't a token available, it will acquire a new token using the username and password supplied. Implementation of this code looks something like this,
class ExchangeServices
{
AuthenticationManager Manager = null;
public ExchangeServices(String AppId, String TenantID)
{
var pcaOptions = new PublicClientApplicationOptions
{
ClientId = AppID,
TenantId = TenantID,
};
var pca = PublicClientApplicationBuilder.CreateWithApplicationOptions(pcaOptions).Build();
Manager = new AuthenticationManager(pca);
}
public static async Task<ExchangeService> GetExchangeService()
{
var ewsScopes = new string[] { "https://outlook.office365.com/EWS.AccessAsUser.All" }
var securePassword = new SecureString();
foreach(char c in Password)
securePassword.AppendChar(c);
var authResult = await Manager.AquireATokenFromCacheOrUsernamePasswordAsync(ewsScopes, Username, securePassword);
ExchangeService exchangeService = new ExchangeService()
{
Credentials = new OAuthCredentials(authResult.AccessToken),
Url = new Uri("https://outlook.office365.com/ews/exchange.asmx");
};
return exchangeService;
}
}
The code above is everything laid out that is needed to create a new authentication manager and use it to get and update new tokens while using EWS services through OAuth. This is the solution that I found to fix the issue described above.
I have migrated the mailbox from exchange server to office 365.
I have already written the code to connect to office 365 using the credentials and so i am able to read all the email that are there in the inbox.Please find the below code
public async System.Threading.Tasks.Task test()
{
var pcaOptions = new PublicClientApplicationOptions
{
ClientId = ConfigurationManager.AppSettings["appId"],
TenantId = ConfigurationManager.AppSettings["tenantId"],
};
var pca = PublicClientApplicationBuilder
.CreateWithApplicationOptions(pcaOptions).Build();
var ewsScopes = new string[] { "https://outlook.office.com/EWS.AccessAsUser.All" };
try
{
string password = "test";
SecureString sec_pass = new SecureString();
Array.ForEach(password.ToArray(), sec_pass.AppendChar);
sec_pass.MakeReadOnly();
// Make the interactive token request
var authResult = await pca.AcquireTokenByUsernamePassword(ewsScopes, "test#demotenant.com", sec_pass).ExecuteAsync();
//var authResult = await pca.AcquireTokenInteractive(ewsScopes).ExecuteAsync();
// Configure the ExchangeService with the access token
var ewsClient = new ExchangeService();
//ewsClient.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, "test#demotenant.onmicrosoft.com");
ewsClient.Url = new Uri("https://outlook.office365.com/EWS/Exchange.asmx");
ewsClient.Credentials = new OAuthCredentials(authResult.AccessToken);
FindItemsResults<Item> result = ewsClient.FindItems(WellKnownFolderName.Inbox, new ItemView(10));
foreach (Item item in result)
{
EmailMessage message = EmailMessage.Bind(ewsClient, item.Id);
String body = message.ConversationTopic;
String from = message.From.Address.ToString();
}
// Make an EWS call
var folders = ewsClient.FindFolders(WellKnownFolderName.MsgFolderRoot, new FolderView(10));
foreach (var folder in folders)
{
Console.WriteLine($"Folder: {folder.DisplayName}");
}
}
catch (MsalException ex)
{
Console.WriteLine($"Error acquiring access token: {ex.ToString()}");
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.ToString()}");
}
}
Now i am looking to add a listener which can run this code whenever a new mail is received in the inbox.
Can someone suggest me on how i can do this.
EWS Streaming or push notifications would be one way to do this in EWS https://learn.microsoft.com/en-us/exchange/client-developer/exchange-web-services/notification-subscriptions-mailbox-events-and-ews-in-exchange. A better approach would be to use Webooks in the Graph API and don't use EWS https://learn.microsoft.com/en-us/graph/api/resources/webhooks?view=graph-rest-1.0 (unless you need you code to run OnPrem). The other thing I would suggest is look at Power Automate (previously Flow) which also has the ability to trigger a large number actions on new Mail receipt.
i'm using Outlook Interop and C# to get some mail information to create an excel report. I have email items list from Sent Items folder and i want to get the email address who i sent the mail. I found the "To" property but only return the person name not the address.
Anyone can please help me to return the email addres from Outlook.MailItem object?
You can ask me if you need more information. Thanks!!!!
Here is my code where i am setting the properties:
foreach (object mail in mails)
//mails is a list from Sent Items folder
{
if (mail is Outlook.MailItem)
{
var item = (Outlook.MailItem)mail;
//i need the address in provider email
var providerEmail = someProperty(????);
var name = item.To;
var request= "Other Request";
var emailDate= item.ReceivedTime;
var status = "Closed";
var responseDate= item.CreationTime;
var reportObject = new ReportModel
{
Email = providerEmail ,
Name = name,
Solicitud = request,
EmailDate = emailDate,
Status = status,
ResponseDate = responseDate
};
}
}
MailItem has Recipients property, you can use it to obtain recipient of each type:
To
Cc
Bcc.
Use recipient.Type to recognize recipient type and recipient.Address to obtain its email address.
Example:
protected override void getRecepients(MailItem OLitem, StringBuilder toStringBuilder,
StringBuilder ccStringBuilder, StringBuilder bccStringBuilder)
{
try
{
var recipients = OLitem.Recipients;
string parent = string.Empty;
foreach (Microsoft.Office.Interop.Outlook.Recipient recipient in recipients)
{
switch (recipient.Type)
{
case (int)Microsoft.Office.Interop.Outlook.OlMailRecipientType.olTo:
toStringBuilder.Append(recipient.Address + ", ");
if (parent == string.Empty)
{
parent = recipient.Address;
}
break;
case (int)Microsoft.Office.Interop.Outlook.OlMailRecipientType.olCC:
ccStringBuilder.Append(recipient.Address + ", ");
break;
case (int)Microsoft.Office.Interop.Outlook.OlMailRecipientType.olBCC:
bccStringBuilder.Append(recipient.Address + ", ");
break;
default:
break;
}
}
}
catch (Exception ex)
{
// do something with error
}
}
This problem is happening for one of our customers and I have been unable to replicate on my side using the same version of Outlook. My customer and I are using Office 365 with Outlook 2016 installed. When he sends an email in our program via Outlook Redemption (a third party program used for Outlook integration), the mail gets stuck in his outbox.
If he double clicks the message (so it pops up in Outlook) he can hit the send button and it will sucessfuly send. If they use an old version of Outlook (2010) this is not a problem. I upgraded them to the the newest version of Outlook Redmeption at the time (released May 07, 2016), though it looks like they just came out with a new version a few days ago. I'll try that soon, but the changelog doesn't mention mail getting stuck in the Outbox.
I also noticed that the emails in his outbox have what appears to be the 'draft' symbol on them, while in my outbox they have a 'sending' symbol on them. This seems important, but I'm not sure what I can do about that.
Also, hitting Send/Receive All Folders does not help.
My code is below. Thank you for any assistance.
public static bool SendMessage(Recipients recipients, string[] addressListReplyTo, string subject, string body, string[] attachments, bool requestReadReceipt, Log log, bool isHtmlBody = false)
{
RDOSession session = null;
RDOMail mail;
RDOFolder folder;
bool result = true;
session = GetSessionAndLogon(log);
if (session == null)
return false;
folder = session.GetDefaultFolder(rdoDefaultFolders.olFolderOutbox);
mail = folder.Items.Add();
if (isHtmlBody)
mail.HTMLBody = body;
else
mail.Body = body;
mail.Subject = subject;
mail.ReadReceiptRequested = requestReadReceipt;
foreach (string attachment in attachments)
{
if (attachment != "")
mail.Attachments.Add(attachment);
}
foreach (string address in addressListReplyTo)
{
if (address != "")
mail.ReplyRecipients.Add(address);
}
foreach (string address in recipients.To)
{
if (address != "")
mail.Recipients.Add(address).Type = 1;
}
foreach (string address in recipients.Cc)
{
if (address != "")
mail.Recipients.Add(address).Type = 2;
}
foreach (string address in recipients.Bcc)
{
if (address != "")
mail.Recipients.Add(address).Type = 3;
}
foreach (RDORecipient recipient in mail.Recipients)
{
if (!OutlookMailEngine64.existsName(recipient.Name, session, log == null ? null : log))
result = false;
}
if (result)
{
try
{
mail.Send();
result = true;
}
catch (System.Runtime.InteropServices.COMException ex)
{
string message = "Error while sending email: " + ex.Message;
if (log != null)
log.Message(message);
if (OutlookMailEngine64.DiagnosticMode)
MessageBox.Show(message);
throw new EmailLibraryException(EmailLibraryException.ErrorType.InvalidRecipient, "One or more recipients are invalid (use OutlookMailEngine64.ValidateAddresses first)", ex);
}
}
if (session.LoggedOn)
session.Logoff();
return result;
}
Keep in mind that message submission is asynchronous, and it will nto be automatically triggered unless you are using the online Exchange store (where store and transport provider are tightly coupled).
You can force send/receive by calling Namespace.SendAndReceive in the Outlook Object Model.
Dmitry worked with me via email. The solution for me was to swap out RDO for the SafeMailItem object. Here is the updated version of my method so you can see the changes:
private static bool SendSafeMessage(Recipients recipients, string[] addressListReplyTo, string subject, string body, string[] attachments, bool requestReadReceipt, Log log, bool isHtmlBody = false)
{
//This method was added because sometimes messages were getting stuck in the Outlook Outbox and this seems to solve that
bool result = true;
Microsoft.Office.Interop.Outlook.Application application = new Microsoft.Office.Interop.Outlook.Application();
Microsoft.Office.Interop.Outlook.NameSpace namespaceMAPI = application.GetNamespace("MAPI");
namespaceMAPI.Logon();
RDOSession session = null;
session = GetSessionAndLogon(log); //TODO: I'm creating a 2nd session here which is wasteful
SafeMailItem safeMail = Redemption.RedemptionLoader.new_SafeMailItem();
Microsoft.Office.Interop.Outlook.MailItem outlookMailItem = (Microsoft.Office.Interop.Outlook.MailItem)application.CreateItem(Microsoft.Office.Interop.Outlook.OlItemType.olMailItem);
safeMail.Item = outlookMailItem;
if (isHtmlBody)
outlookMailItem.HTMLBody = body;
else
safeMail.Body = body;
outlookMailItem.Subject = subject;
outlookMailItem.ReadReceiptRequested = requestReadReceipt;
foreach (string attachment in attachments)
{
if (attachment != "")
safeMail.Attachments.Add(attachment);
}
foreach (string address in addressListReplyTo)
{
if (address != "")
safeMail.ReplyRecipients.Add(address);
}
foreach (string address in recipients.To)
{
if (address != "")
safeMail.Recipients.Add(address).Type = 1;
}
foreach (string address in recipients.Cc)
{
if (address != "")
safeMail.Recipients.Add(address).Type = 2;
}
foreach (string address in recipients.Bcc)
{
if (address != "")
safeMail.Recipients.Add(address).Type = 3;
}
foreach (Microsoft.Office.Interop.Outlook.Recipient recipient in outlookMailItem.Recipients)
{
if (!OutlookMailEngine64.existsName(recipient.Name, session, log == null ? null : log))
result = false;
}
if (result)
{
try
{
safeMail.Send();
result = true;
}
catch (System.Runtime.InteropServices.COMException ex)
{
string message = "Error while sending email: " + ex.Message;
if (log != null)
log.Message(message);
if (OutlookMailEngine64.DiagnosticMode)
MessageBox.Show(message);
throw new EmailLibraryException(EmailLibraryException.ErrorType.InvalidRecipient, "One or more recipients are invalid (use OutlookMailEngine64.ValidateAddresses first)", ex);
}
}
if (session.LoggedOn)
session.Logoff();
namespaceMAPI.Logoff();
return result;
}