简体   繁体   中英

Microsoft Graph API - Sending email as another user

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).

Whenever you are using delegated permissions (ie 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://docs.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://docs.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)
        {

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;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM