I have a question about Microsoft Graph API for C# code. Is it possible to get all messages which I have in Outlook?
I found a good article, but I'm still not getting an answer to my question. The article:
Get all email message using Microsoft Graph API in c#
(UPDATED) I found a good video about how to connect to Microsoft Graph: https://www.youtube.com/watch?v=acnFrkBL1kE&ab_channel=Microsoft365Developer
I have still the same question. I can get 1000 emails for call. Is there any way to get all messages from my Outlook? They should be from all my folders.
My new code for call 1000 messages:
public async Task<(IEnumerable<Message> Messages, string NextLink)> GetUserMessagesPage(
string nextPageLink = null, int top = 1000)
{
IUserMessagesCollectionPage pagedMessages;
if(nextPageLink == null)
{
pagedMessages = await _graphServiceClient.Me.Messages.Request().Select(msg => new
{
msg.Subject,
msg.BodyPreview,
msg.ReceivedDateTime
}).OrderBy("receivedDateTime desc").Top(1000).GetAsync();
}
else
{
var messagesCollectionRequest = new UserMessagesCollectionRequest(nextPageLink, _graphServiceClient, null);
pagedMessages = await messagesCollectionRequest.GetAsync();
}
return (Messages: pagedMessages, NextLink: GetNextLink(pagedMessages));
}
(UPDATED) I have tried also this:
pagedMessages = await _graphServiceClient.Users["email#.com"].Messages.Request().Select(msg => new { msg.Subject}).Top(1000).GetAsync();
messages.AddRange(pagedMessages.CurrentPage);
while (pagedMessages.NextPageRequest != null)
{
pagedMessages = await pagedMessages.NextPageRequest.GetAsync();
messages.AddRange(pagedMessages.CurrentPage);
}
It was mentioned here: https://github.com/microsoftgraph/microsoft-graph-docs/blob/main/api-reference/beta/api/user-list-messages.md
From: Microsoft Graph REST API - Get message Documentation
There are two scenarios where an app can get a message in another
user's mail folder:
If the app has application permissions, or,
If the app has the appropriate delegated permissions from one user, and another user has shared a mail folder with that user, or,
has given delegated access to that user. See details and an
example.
Permissions
One of the following permissions is required to call this API. To
learn more, including how to choose permissions, see
Permissions.
Delegated (work or school account) - Permissions: Mail.ReadBasic, Mail.Read
Delegated (personal Microsoft account) - Permissions: Mail.ReadBasic, Mail.Read
Application - Permissions: Mail.ReadBasic.All, Mail.Read
Related
We have a bot design in Bot framework-4 using .Net c# sdk. This bot is hosted on IIS and available
on different channel such as Directline, MS Teams etc. We want to send proactive messages to all the user in MS teams to notify them irrespective of if they communicated with bot or not. The Proactive messages will be 1:1 message.
After doing lot of R&D we found that we will be only able to send Proactive message to user only when there conversation reference is present. (let me know if other way is also possible.)
Using below link and Sample to send Proactive message to user:
Proactive Message Sample
Document Referred
We are using cosmos DB container and auto save middleware for bot conversation state and user state management.
Code in ConfigureServices method of Startup.cs file:
var blobDbService = botConfig.Services.FirstOrDefault(s => s.Type == ServiceTypes.BlobStorage) ?? throw new Exception("Please configure your Blob service in your .bot file.");
var BlobDb = blobDbService as BlobStorageService;
var dataStore = new AzureBlobStorage(BlobDb.ConnectionString, BlobDb.Container);
var userState = new UserState(dataStore);
var conversationState = new ConversationState(dataStore);
services.AddSingleton(dataStore);
services.AddSingleton(userState);
services.AddSingleton(conversationState);
services.AddSingleton<ConcurrentDictionary<string, ConversationReference>>();
services.AddSingleton(new BotStateSet(userState, conversationState));
services.AddBot<EnterpriseTiBOT>(options =>
{
// Autosave State Middleware (saves bot state after each turn)
options.Middleware.Add(new AutoSaveStateMiddleware(userState, conversationState));
}
Code to Store Conversation Reference for each user:
private void AddConversationReference(Activity activity)
{
var conversationReference = activity.GetConversationReference();
_conversationReferences.AddOrUpdate(conversationReference.User.Id, conversationReference, (key, newValue) => conversationReference);
}
protected override async Task OnStartAsync(DialogContext dc, CancellationToken cancellationToken = default(CancellationToken))
{
AddConversationReference(dc.Context, cancellationToken);
}
Code in notifyContoller is same as the code from GitHub Sample. There are 2 issues we are facing :
The concurrent dictionary having conversation reference become blank when the IIS pool is recycled and we are not able to send the proactive message to the user, how to store it in Blob storage and access the same in Notify controller?
We want to send proactive message to all the user whether they have communicated with bot or not, any way to achieve this? Tried 3rd approach from this article. But the challenge is, we are not able to send message to user based on User ID or user principle name.
There are multiple ways to store the conversation and user info. You should store these details in more persistent place rather than in memory. Here is a sample app code which stores user detail and along with the conversation Id in cosmos DB at the time of installation of the app. You could look into the implementation part. It can be any storage (blob, SQL).
For sending proactive message, User must have access to your app. You could make your app install for everyone in the tenant from Teams admin portal. Here is a reference documentation for managing the app from admin portal.
You need to have the conversation ID (between a bot and user) for sending a proactive message. And the conversation Id is created when the bot is installed for an user Or in team/group where user is part of.
I want to add comment to the answer but I do not have enough reputation to do so. Just to eleborate point 1 , if you want to ask where can we get and save the conversation refences, you can get it via method named: OnConversationUpdateActivityAsync.
It took me some time to get to this, so I think it is useful to share.
You can get a lot of information eg user ID, channel ID from the activity, here is some sample code:
public class ProactiveBot : ActivityHandler
{
...
...
protected override Task OnConversationUpdateActivityAsync(ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
var conversationReferences = turnContext.Activity.GetConversationReference();
//this is your user's ID
string userId = conversationReference.User.Id;
//this is the bot's ID and will be the same for all activities under same bot.
string botId = conversationReference.Bot.Id;
...
...
...
}
}
I am creating a UWP application and I want to use the Onedrive API so that users can save a copy of their files in their Onedrive account, but I don't get it, so far I have only managed to login what I want is:
upload files
download files
and synchronize if any of them is modified
Create folders
Delete Files
This code achieves the login, but I can not move beyond this, as would proceed for upload files or download them
private async void btn_Login_Click(object sender, RoutedEventArgs e)
{
if (this.oneDriveClient == null)
{
try
{
// Setting up the client here, passing in our Client Id, Return Url,
// Scopes that we want permission to, and building a Web Broker to
// go do our authentication.
this.oneDriveClient = await OneDriveClient.GetAuthenticatedMicrosoftAccountClient(
clientId,
returnUrl,
scopes,
webAuthenticationUi: new WebAuthenticationBrokerWebAuthenticationUi());
// Show in text box that we are connected.
txtBox_Response.Text = "We are now connected";
// We are either just autheticated and connected or we already connected,
// either way we need the drive button now.
btn_GetDriveId.Visibility = Visibility.Visible;
}
catch (OneDriveException exception)
{
// Eating the authentication cancelled exceptions and resetting our client.
if (!exception.IsMatch(OneDriveErrorCode.AuthenticationCancelled.ToString()))
{
if (exception.IsMatch(OneDriveErrorCode.AuthenticationFailure.ToString()))
{
txtBox_Response.Text = "Authentication failed/cancelled, disposing of the client...";
((OneDriveClient)this.oneDriveClient).Dispose();
this.oneDriveClient = null;
}
else
{
// Or we failed due to someother reason, let get that exception printed out.
txtBox_Response.Text = exception.Error.ToString();
}
}
else
{
((OneDriveClient)this.oneDriveClient).Dispose();
this.oneDriveClient = null;
}
}
}
}
I created a sample repository in github:Onedrive Sync Files Sample
I have already tried using Dropbox, Gdrive, but its implementation for UWP seems to be much more complex, so I chose OneDrive. any answer will be very helpful thanks in advance
How to Sync files with OneDrive API in C#
For using OneDrive, we suggest you implement OneDrive feature with OneDrive Service that is part of Windows Community Toolkit .
Getting Started
To use the OneDrive API, you need to have an access token that authenticates your app to a particular set of permissions for a user. In this section, you'll learn how to:
Register your application to get a client ID and a client secret.
Sign your user in to OneDrive with the specified scopes using the token flow or code flow.
Sign the user out (optional).
And this is official code sample that you could refer.
I created an app with the Application Permissions MailboxSettings.ReadWrite.
I then built a Web API to create subscriptions to mailbox folders.
I am successfully able to get the Auth Token and listen to the test notification from the app. But when I try to create a subscription I get the following error:
{
Code: ExtensionError
Message: Operation: Create; Exception: [Status Code: Unauthorized; Reason: Unauthorized]
}
This is how I am creating a subscription:
var subscription = new Subscription
{
Resource = $"users/{CurrentUserId}/mailFolders('Inbox')/messages",
ChangeType = "created,updated",
NotificationUrl = ConfigurationManager.AppSettings["ida:NotificationUrl"],
ClientState = Guid.NewGuid().ToString(),
ExpirationDateTime = DateTime.UtcNow + new TimeSpan(0, 0, 15, 0)
};
var newSubscription = await graphClient.Subscriptions.Request().AddAsync(subscription);
My question is, which permissions does my App need to have create a subscription?
I am using Microsoft Graph access without a user.
https://learn.microsoft.com/en-us/graph/auth-v2-service. I want to run my API unattended.
According to the doc, the Mail.Read permission will be enough. You need admin consent the permission for the AD App, see this link.
In our application, we need to send notifications to users by email for various event triggers.
I'm able to send email if I send as "Me" the current user, but trying to send as another user account returns an error message and I'd prefer it if notifications didn't come users' themselves and may contain info we don't want floating around in Sent folders.
What works:
await graphClient.Me.SendMail(email, SaveToSentItems: false).Request().PostAsync();
What doesn't work:
string FromUserEmail = "notifications#contoso.com";
await graphClient.Users[FromUserEmail].SendMail(email, SaveToSentItems: false).Request().PostAsync();
Also tried using the user object id directly:
await graphClient.Users["cd8cc59c-0815-46ed-aa45-4d46c8a89d72"].SendMail(email, SaveToSentItems: false).Request().PostAsync();
My application has permissions for the Graph API to "Send mail as any user" enabled and granted by the owner/administrator.
The error message returned by the API:
Code: ErrorFolderNotFound Message: The specified folder could not be
found in the store.
I thought this error might have been because the notifications account didn't have a sent folder, so I set the SaveToSentItems value to false, but I still get the same error.
Are there any settings I need to check on the account itself to allow the app to send mail on this account or should this work?
I have checked out the documentation here:
https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/user_sendmail
Which appears to support what I'm trying to do, but doesn't reference any folder except for the sent items folder which I'm telling the API not to save to anyway.
We aren't intending to impersonate any actual user here, just send notification emails from within the app from this specific account (which I know is technically impersonation, but not of a real entity).
So like Schwarzie2478 we used a noreply#ourcompany.com address. But our AD is federated which means you can't use Username\Password auth and we didn't want to use the Application Mail.Send permission since it literally can send as anyone and there is no way IT Security would let that fly. So we used Windows Authentication instead.
This requires that you grant consent to the app to use the mail.send and user.read delegate permissions by going to https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/authorize?client_id={clientId}&response_type=code&scope=user.read%20mail.send and logging in with the windows user that the app will run as.
More info on using windows auth here: https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/Integrated-Windows-Authentication
// method call
var t = SendEmailUsingGraphAPI();
t.Wait();
// method
static async Task<Boolean> SendEmailUsingGraphAPI() {
// AUTHENTICATION
var tenantID = "YOUR_TENANT_ID"; //azure ad tenant/directory id
var clientID = "YOUR_APPS_CLIENT_ID"; // registered app clientID
var scopes = "user.read mail.send"; // DELEGATE permissions that the request will need
string authority = $"https://login.microsoftonline.com/{tenantID}";
string[] scopesArr = new string[] { scopes };
try {
IPublicClientApplication app = PublicClientApplicationBuilder
.Create(clientID)
.WithAuthority(authority)
.Build();
var accounts = await app.GetAccountsAsync();
AuthenticationResult result = null;
if (accounts.Any()) {
result = await app.AcquireTokenSilent(scopesArr, accounts.FirstOrDefault())
.ExecuteAsync();
}
else {
// you could acquire a token by username/password authentication if you aren't federated.
result = await app.AcquireTokenByIntegratedWindowsAuth(scopesArr)
//.WithUsername(fromAddress)
.ExecuteAsync(CancellationToken.None);
}
Console.WriteLine(result.Account.Username);
// SEND EMAIL
var toAddress = "EMAIL_OF_RECIPIENT";
var message = "{'message': {'subject': 'Hello from Microsoft Graph API', 'body': {'contentType': 'Text', 'content': 'Hello, World!'}, 'toRecipients': [{'emailAddress': {'address': '" + result.Account.Username + "'} } ]}}";
var restClient = new RestClient("https://graph.microsoft.com/v1.0/users/" + result.Account.Username + "/sendMail");
var request = new RestRequest(Method.POST);
request.AddHeader("Content-Type", "application/json");
request.AddHeader("Authorization", "Bearer " + result.AccessToken);
request.AddParameter("", message, ParameterType.RequestBody);
IRestResponse response = restClient.Execute(request);
Console.WriteLine(response.Content);
}
catch (Exception e) {
Console.WriteLine(e.Message);
throw e;
}
return true;
}
Whenever you are using delegated permissions (i.e. when a user is logged in), even though your admin has consented to the Mail.Send.Shared, it does NOT grant access to all mailboxes in the tenant. These OAuth permissions do not override the permissions (and restrictions) in place for the user.
If the user is not already configured with permissions to be able to "Send As" the notifications#contoso.com user, then you'll see this error.
To make it work, you'd need to actually grant "Send As" rights to all users that will be using your application.
This is a subtle thing, and granted it's a bit confusing. In the Azure portal, the permissions have slightly different descriptions, depending on if you're looking at the Application Permissions or the Delegated Permissions.
Application: Send mail as any user
Delegated: Send mail on behalf of others
Since you're using delegated, the permission doesn't allow you to send as any user, only send on behalf of any folks that the logged on user has rights to send as.
Another approach you could use here to avoid having to grant these rights to all users (which would allow them to send via Outlook, etc.) would be to have your backend app use the client credentials flow to get an app-only token. In that case, the app itself would have the permission to send as any user.
I don't know what others will have done for this, but I contacted Microsoft about this exact scenario: I want to send a mail as a fixed user ( noreply#mycompany.com) which has a mailbox in Azure. I want to send this mail from different applications or services.
The person there told me that sending a mail with no user logging in, is only possible with an delegated user token.
So we configured our application as an Native application in Azure like for mobile apps. Logging in for this application with the technical user during a setup phase gives me a delegated user token for that specific user which can be stored in a mailing service or component. This token does not expire ( at least not until the security changes of the user like password or something) and can be used to call the graph api to send mails when you give permission for this account to be sending mails from.
Next to that we even associated other shared mailboxes to this accounts to be able to send mails for those mailboxes too.
Documentation:
First You need a native app registration in Azure ( not an Web API):
https://learn.microsoft.com/en-us/azure/active-directory/develop/native-app
This app only requires an one-time login and approval from an user to get a token which can represent that user indefinitly. We set up a mail user account to be used for this. That token is then used to get access token to Graph Api for sending mails and such
Token Handling example:
https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/token-cache-serialization
With an identitytoken stored ( usually a .cache file somewhere) you can request an accesstoken:
Identity Client:
https://learn.microsoft.com/en-us/dotnet/api/microsoft.identity.client.publicclientapplication?view=azure-dotnet
_clientApp = new PublicClientApplication(ClientId, "https://login.microsoftonline.com/{xxx-xxx-xx}, usertoken,...
authResult = await _clientApp.AcquireTokenSilentAsync(scopes,...
private static string graphAPIEndpoint = "https://graph.microsoft.com/v1.0/me";
//Set the scope for API call to user.read
private static string[] scopes = new string[] { "user.read", "mail.send" };
private const string GraphApi = "https://graph.microsoft.com/";
var graphclient = new GraphServiceClient($"{GraphApi}/beta",
new DelegateAuthenticationProvider(
(requestMessage) =>
{
// inject bearer token for auth
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", authResult.AccessToken);
return Task.FromResult(0);
}));
var sendmail = graphclient.Users[User].SendMail(mail), true);
try
{
await sendmail.Request().PostAsync();
}
catch (Exception e)
{
I am trying to use the Microsoft Bot Framework DirectLine API to read and add messages to existing conversations between other users and my bot. From what I've read I believe this should be possible when using the master-secret but it's just not working for me. I'm using a WebAPI to try and access two of my existing conversations (on Facebook & Skype) as follows:
[HttpPost]
[Route("remind")]
public string Remind()
{
var secret = System.Configuration.ConfigurationManager.AppSettings["secret"];
var uri = new Uri("https://directline.botframework.com/");
var creds = new DirectLineClientCredentials(secret);
DirectLineClient client = new DirectLineClient(uri, creds);
Conversations convs = new Conversations(client);
var conversationIDs = new string[] { "0000000000000000-0000000000000000", "00:0123456789abcdefghijklmnopqrstuvwxyz0123456789-A-_0123456798ABCDEF" }; // Existing Facebook & Skype conversations
// Send a message to each conversation:
foreach (var conversationID in conversationIDs)
{
Message message = new Message(conversationId: conversationID, fromProperty: "My Bot", text: "Hey dude, remember that thing!");
Console.WriteLine(message.Text);
convs.PostMessage(conversationID, message); // FAILS - This executes but doesn't do anything.
}
// Try reading the messages from a conversation (just to test if it's working):
string waterMark = null;
var set = convs.GetMessages(conversationIDs[0], waterMark); // FAILS - This fails with a 404 not found.
waterMark = set.Watermark;
return "Done :-)";
}
It fails silently calling PostMessage() and fails with a 404 for the GetMessages(). I seem to be doing the right thing, the bot is live etc and works very well in Facebook & Skype separately from the DirectLine API. It only works if I create a new conversation using the DirectLine API, I can then access its messages and post new messages to it.
This question sort of helps but doesn't quite tell me what to do to fix it:
Difficulty accessing messages in an existing conversation in Microsoft Bot Framework
Any help would be much appreciated.
Thanks
For security reasons, you can't use DirectLine to spy on messages from another conversation. For the scenario you describe (escalating to a human) there a number of different ways to approach this. One is to have your bot broker conversations between the accounts (i.e. Facebook End User <-> Your Bot <-> Facebook Support Person). Each is talking to the bot, and the bot passes the message through to the other user. (Could also be Facebook User <-> Your Bot <-> Skype User) Your bot would have to store last n messages to provide context. Alternatively, I've seen folks build their own customer support chat interface using direct line that sits on the far side. Hope this helps