Graph Mail.Send

Microsoft hat das Commandlet "Send-Mailmessage" abgekündigt und verweist auf andere Hilfsprogramme. Auch EWS ist nicht mehr der bevorzugte Weg aber mit Exchange Online kann ich auch per Graph eine Mail versenden.

Graph und App

Per Graph kann ich über eine einfache REST-API per HTTPS unterschiedlichste Aktionen auslösen und Daten verändern. Aber dazu reicht es nicht, einen Benutzernamen und ein Kennwort zu haben. Für Graph brauche ich ein Authentication-Token, welches mir der Microsoft Authentifizierungsdienst gibt. Dieser fordert aber nicht nur meine Anmeldedaten sondern Auch eine AppID. Sie müssen daher erst eine App mit den erforderlichen Berechtigungen definieren. Das erscheint komplexer als es ist, aber sie müssen sich dennoch erst einmal mit dem Graph Token beschäftigen. Für den Versand von Mails gibt es über Graph dann zwei Optionen:

  • Delegated
    Sie nutzen die App, bzw. hier das PowerShell-Skript, um mit ihren eigenen Benutzernamen und Kennwort ein Token zu erhalten und die Mail zu senden.
  • Application
    Bei dieser Anmeldung gewähren Sie einer Applikation, d.h. ihrem Code, das Recht eine Mail mit einem beliebigen Benutzer zu senden. Da müssen wir gleich noch mal drüber reden.

Dies beschreibt Microsoft auch auf der "Permission"-Seite:


https://docs.microsoft.com/en-us/graph/api/user-sendmail

Impersonate oder Delegate

Die Herausforderung ist nun, den richtigen Weg mit den richtigen Rechten zu nutzen. Ein Skript im Arbeitsbereich des Benutzers kann mit "delegate" problemlos arbeiten, während ein Service mit "Application permission" den Benutzern die Arbeit abnehmen kann, aber ohne weitere Kontrolle sehr viel Rechte hat. Drei Szenarien sind hier denkbar

Szenario Zufriff Beschreibung

Benutzer sendet Mail mit einem Programm

Delegate

An einem Client ist ein Anwender mit seinen Credentials angemeldet und startet eine Software. Diese Software möchte nun als Benutzer eine Mail versenden. Früher hat die Software dazu einfach per MAPI eine Mail über das lokale Outlook eingeliefert und sie wurde versendet. Heute sind aber Apps teilweise "im Browser" und kommen nicht mehr an Outlook oder auf dem Smartphone/Tablet mit IOS/Android gibt es keine MAPI-Schnittstelle. Dann muss die Applikation selbst die Mail senden. Natürlich könnte der Anwender seine Anmeldedaten (Username/Kennwort) an die Applikation verraten, damit diese dann eine Mail versendet. Besser ist es aber, wenn die Applikation das Recht bekommt (Consent), als Benutzer eine Mail zu senden

Service arbeitet ohne interaktiven Benutzer

Application

Wenn sie nun auf einem Server einen Dienst bereitstelle, der ohne Interaktion mit dem Anwender funktionieren soll, dann kann der Zugriff nicht über "Delegate" erfolgen. Hier ist die "Application Permission" der richtige Weg. Die App selbst ist einen Service Principal aber hat natürlich kein Postfach. Ohne weitere Einschränkungen bedeutet dieses "Mail.Send"-Rechte aber, dass sich die App als jeder Benutzer ausgeben kann. Das ist meist nicht erwünscht. In Graph ist dies aber nicht steuerbar aber Graph nutzt im Hintergrund Exchange zum Versand und dort können Sie über die ApplicationAccessPolicy steuern.

Unattendand Versand als ein Postfach

Application

Soll nun ein Service quasi nur mit genau einer Mailadresse versenden, für die es auch ein Postfach gibt, dann tippen viele erst einmal auf "Delegate". Aber dann müsste der Service auch die Zugangsdaten zu dem Benutzer haben, der aber bei einer Absicherung durch 2FA nicht ohne Interaktion nutzbar ist. Sie könnten nun über Conditional Access (Azure AD P1-Lizenz) eine Ausnahme für diese App machen oder sie geben der App zwar das Recht als "Application" zu arbeiten und beschränken den Zugriff mittels ApplicationAccessPolicy auf diese eine Mailbox

 

Graph können Sie aber nicht nutzen, um z.B. per Skript einfach eine Mail mit einem beliebigen Absender an Exchange zu übermitteln. Es ist damit nicht wirklich ein "Send-Mailmessage"-Ersatz, mit dem Sie sogar ohne Authentifizierung und beliebiger Absenderadresse per SMTP eine Mail einliefern, zumindest solange der Spamfilter nichts dagegen hat.

Der Versand ist nur mit einer Adresse eines Postfachs in Exchange Online möglich. Ein Versand als Kontakt, Verteiler o.ä. geht nicht.

App anlegen

Damit sich ihre PowerShell als "App" anmelden kann, müssen Sie mal wieder im Azure AD ihres Tenants eine Application anlegen, die neben einem Namen und einem Secret" auch das Recht "SendMail" bekommt. Der Startpunkt ist wieder das Azure Portal unter https://portal.azure.com

  • Neue App anlegen
    Gegen Sie als Tenant Admin auf https://aad.portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredApps und legen Sie eine App an

    Sie brauchen keine Umleitungsurl o.ä.
  • Kopieren Sie sich die AppID
    Das ist der spätere "Username", mit der sich die Application ausweist
  • Legen Sie ein Secret an
    Über "Zertifikate & Geheimnisse" legen Sie ein neues Secret an.

    Kopieren Sie sich das Kennwort. Es wird nur einmal angezeigt. Wenn Sie es vergessen, verlieren oder es abgelaufen ist, dann dürfen Sie bei allen Skripten den Wert wieder anpassen.

    Das ist natürlich kein Problem für eine Applikation, die Sie über einen AppStore verteilen und jederzeit aktualisieren können. Für PowerShell-Skripte im Feld ist es aber kniffliger und Sie wollten den Secret sicher nicht per DNS-TXT-Record veröffentlichen. Alternativ können Sie natürlich auch ein Zertifikat hochladen und später im Skript nutzen.
  • Berechtigungen geben
    Zuletzt geben wir der App noch die Berechtigungen. Ich habe hier der App zum Test alle "Mail.Send"-Rechte gegeben

    Sie sehen aber auch, dass die "App-Permission" noch durch den Administrator bestätigt werden muss. Das kann auch hier erfolgen. Wenn ein Benutzer das Script verwenden will, kann er selbst die Zustimmung erteilen.

Damit haben wir alle Vorarbeiten erledigt.

Ich werde im weiteren nicht beschreiben, wie ich als Anwender ein PowerShell-Script eines Entwicklers nutze, um eine Mail per Graph zu senden. das ist ein eher seltener Fall und der Benutzer könnte sich aus dem Skript auch die AppID und die Secrets holen. Diese Seite beschäftigt sich allein um die Nutzung der GraphAPI und "Send.Mail" mit der Application Permission.

Für die weiteren Schritte gehe ich davon aus, dass Sie eine App samt Secret mit entsprechender App-Permission angelegt haben

Application Permission einschränken

Wen ihre Lösung nicht mit "Delegated" sondern mit "Application"-Permission arbeitet, und sie keine Rechte zum Versand für "alle" Postfächer benötigen, dann sollten Sie die Berechtigungen weiter einschränken.

Token anfordern

Der erste Schritt ist dann die Erlangung eines Tokens. Das ist mit einer App recht einfach, da ich keinen Consent eines Benutzers oder eine Callback-URL einer Webapplication unterstützen muss. Details dazu finden Sie auf Graph Token. Der Code ist immer ähnlich

param (
   $LoginUrl="https://login.microsoftonline.com",
   $AppID="hier muss ihre AppID rein",
   $AppSecret= "Und hier das entsprechende Secret",
   $TenantName="<hiermussihrtenantrein>.onmicrosoft.com"
)

$authresponse=Invoke-RestMethod `
                 -Uri "$($LoginUrl)/$($TenantName)/oauth2/v2.0/token" `
                 -Method POST `
                 -ContentType "application/x-www-form-urlencoded" `
                 -body "client_id=$($AppID)
                        &scope=https%3A%2F%2Fgraph.microsoft.com%2F.default
                        &client_secret=$($AppSecret)
                        &grant_type=client_credentials
                        &api-version=1.0"
$accesstoken = $authresponse.access_token

Wenn Sie das MSADAL.PS-Modul nutzen, dann funktioniert dies auch wie folgt:

$AppID = "<Ihre Appid>"
$TenantID = "<ihrtenantname>.onmicrosoft.com"
$ClientSecret = ConvertTo-SecureString "IhrClientSecret" -AsPlainText -Force
$Scope = "https://outlook.office.com/.default"

Clear-MsalTokenCache
$Token = Get-MSALToken `
            -ClientId $AppID  `
            -ClientSecret $ClientSecret  `
            -TenantId $TenantID  `
            -Scope $Scope
$AccessToken = $Token.AccessToken

Nun sollten Sie in $accesstoken ein Zugriffstoken haben, welches Sie über JWT-Decoder auch prüfen können.

Achtung: Wer im Besitz dieses Tokens ist, hat für die Dauer der Gültigkeit die entsprechenden Berechtigungen.

Graph OnBehalf

Ich habe meiner App das "Mail.Send"-Recht gegeben und so war es keine große Überraschung, dass ich eine Mail senden konnte. Hier der einfache Code:

Der Code funktioniert nur, wenn Sie in der zweiten Zeile eine Mailadresse eines Exchange Online Postfachs ihres Tenants und bei E-Mail-Addresses eine passende Empfänger-SMTP-Adresse eintragen.

$sendmail = Invoke-RestMethod `
                 -Uri "https://graph.microsoft.com/v1.0/users/usergraphsendmail%40x.msxfaq.de/sendMail" `
                 -Method POST `
                 -ContentType "application/json" `
                 -Header @{ 'Authorization' = "Bearer $($accesstoken)" } `
                 -Body "{
                           ""message"": {
                              ""subject"": ""Das ist der Betreff"",
                              ""body"": {
                             ""contentType"": ""HTML"",
                             ""content"": ""<h1>Headline1</h1><p>Message</p>""
                           },
                           ""toRecipients"": [
                             {
                           ""emailAddress"": {
                             ""address"": ""usergraphsendmail@msxfaq.de""
                           }
                             }
                           ],
                           ""internetMessageHeaders"":[
                             {
                           ""name"":""x-msxfaq-agent"",
                           ""value"":""GraphTest""
                             },
                             {
                           ""name"":""x-msxfaq-version"",
                           ""value"":""1.0""
                             }
                           ]
                             }
                           }"

Die Mail in meinem Postfach ist umgehend zugestellt worden.

Da ich ein "authentifizierter Absender" war, sollte die Mail wie eine per Outlook gesendete Mail behandelt werden.

Messagetracking

Daher habe ich mir den Header angeschaut (etwas gekürzt):

Received: from AM0PR04MB7059.eurprd04.prod.outlook.com (2603:10a6:208:192::14)
 by AM0PR04MB5489.eurprd04.prod.outlook.com with HTTPS; Sat, 13 Nov 2021
 22:09:25 +0000
Authentication-Results: dkim=none (message not signed)
 header.d=none;dmarc=none action=none header.from=msxfaq.de;
Received: from AM0PR04MB5489.eurprd04.prod.outlook.com (2603:10a6:208:10f::25)
 by AM0PR04MB7059.eurprd04.prod.outlook.com (2603:10a6:208:192::14) with
 Microsoft SMTP Server (version=TLS1_2,
 cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.4690.15; Sat, 13 Nov
 2021 22:09:24 +0000
Received: from AM0PR04MB5489.eurprd04.prod.outlook.com
 ([fe80::e05f:a073:dc0:d591]) by AM0PR04MB5489.eurprd04.prod.outlook.com
 ([fe80::e05f:a073:dc0:d591%3]) with mapi id 15.20.4669.021; Sat, 13 Nov 2021
 22:09:23 +0000
Content-Type: application/ms-tnef; name="winmail.dat"
Content-Transfer-Encoding: binary
From: "Carius, Frank " <usergraphsendmail@msxfaq.de>
To: "Carius, Frank " <usergraphsendmail@msxfaq.de>
Subject: Das ist der Betreff
Thread-Topic: Das ist der Betreff
Thread-Index: AQHX2Nsd8rubYJ6npEOCmpwknsO46A==
Date: Sat, 13 Nov 2021 22:09:23 +0000
Message-ID: <AM0PR04MB5489485D535EF8F869E97EF6FF969@AM0PR04MB5489.eurprd04.prod.outlook.com>
Accept-Language: de-DE, en-US
Content-Language: en-US
X-MS-Exchange-Organization-SCL: -1
x-msxfaq-agent: GraphTest
x-msxfaq-version: 1.0
MIME-Version: 1.0
X-MS-Exchange-Organization-MessageDirectionality: Originating
X-MS-Exchange-Organization-AuthMechanism: 04
X-MS-Exchange-Organization-Network-Message-Id:
 2762549f-e686-470a-754b-08d9a6f23feb
X-MS-PublicTrafficType: Email
Return-Path: usergraphsendmail@msxfaq.de
X-msxfaq-EXORules: 1
X-Microsoft-Antispam: BCL:0;
X-Forefront-Antispam-Report:
 CIP:255.255.255.255;CTRY:;LANG:de;SCL:-1;SRV:;IPV:NLI;SFV:SKI;H:AM0PR04MB5489.eurprd04.prod.outlook.com;PTR:;CAT:NONE;SFS:;DIR:INB;
X-MS-Exchange-CrossTenant-AuthAs: Internal
X-MS-Exchange-CrossTenant-MailboxType: HOSTED
X-MS-Exchange-Transport-EndToEndLatency: 00:00:01.9229242

Meine beide "Custom Header" mit den Namen "x-msxfaq-agent: GraphTest" und "x-msxfaq-version: 1.0" sind sichtbar aber ansonsten kann ich nicht erkennen, dass die Nachicht per Graph eingeliefert wurde. Stattdessen steht im ersten "Received:"-Header sogar ein "with mapi id 15.20.4669.021". Anhand des Headers "X-msxfaq-EXORules: 1" sehe ich, dass auch die Transportregeln zugeschlagen haben.

Messagetracking AdminCenter

Mit dem Header erwarte ich auch im Messagetracking keine Überraschungen:

Das Messagetracking liefert hier aber die einliefernde IP-Adresse "20.190.160.97" mit, welche zu Azure in Amsterdam gehört. Im Messagetracking wird hier eine IPv6-Adresse als erster Hop angezeigt.

Sonderfälle

Das Versenden einer Mail als privilegierte App über Graph ist ja einfach. Ich habe aber einige weitere Fälle durchgespielt und die Fehlermeldungen dokumentiert. Während meiner Testserien habe ich einige Fehler produziert, deren Ursache und die Meldung ich hier gerne teile:

Beschreibung Fehlermeldung

"me/sendMail"-Fall

Ein Anwender kann über die URL "me/sendMail" eine Mails von seinem eigenen Postfach senden. Wer ist "me" wenn ich mit Application Permission arbeite?

Es geht einfach nicht.

{"error":{
   "code":"BadRequest",
   "message":"/me request is only valid with delegated authentication flow.",
   "innerError":{
      "date":"2021-11-13T22:05:44",
      "request-id":"df29e2d4-f51f-48aa-a747-09886bcf0e24",
      "client-request-id":"df29e2d4-f51f-48aa-a747-09886bcf0e24"}
   }
}

Senden als anderer Benutzer

Wenn ich schon als Frank Carius sende, dann sollte ich auch als jeder andere Benutzer senden können. Das hat fehlerfrei funktioniert und sogar der Displayname wurde eingefügt.

Kein Fehler

Senden mit sekundärer SMTP-Adresse

Viele Benutzer haben nicht nur eine sondern mehrere SMTP-Adressen. Ich habe diese Adresse in der URL versucht und es hat auf Anhieb funktioniert. Graph scheint also nicht den UPN sondern die ProxyAdresses auszuwerten.

Wenn ich schon als Frank Carius sende, dann sollte ich auch als jeder andere Benutzer senden können. Das hat fehlerfrei funktioniert und sogar der Displayname wurde eingefügt.

Kein Fehler

Mailalias oder ungültige Adresse

Dann habe ich statt meines UPN/Mailadresse den Mailnickname versucht.

Das hat nicht funktioniert.

{"error":{
   "code":"ResourceNotFound",
   "message":"Resource could not be discovered.",
   "innerError":{
       "date":"2021-11-14T00:08:54",
      "request-id":"8548c244-4ede-491f-8aa4-b7e0e0ab0ac6",
      "client-request-id":"8548c244-4ede-491f-8aa4-b7e0e0ab0ac6"}
   }
}

Versand als Verteiler oder externe Adresse, Kontakt, Public Folder, Microsoft Team/Office Group, Remote Mailbox

Der Versand mit der SMTP-Adresse eines Verteilers hat nicht funktioniert. Die Fehlermeldung war auch beim Versand mit einer beliebigen SMTP-Adresse aus anderen Tenants oder dem Internet nicht möglich.

{"error":{
   "code":"ErrorInvalidUser",
   "message":"The requested user 'dl@x.msxfaq.de' is invalid.",
   "innerError":{"date":"2021-11-14T00:13:50",
      "request-id":"5f3d3171-7f57-42ca-8be9-e3b9a20b9242",
      "client-request-id":"5f3d3171-7f57-42ca-8be9-e3b9a20b9242"}
  }
}

Versand als Raum

Der Versand mit der SMTP-Adresse eines Raums hat hingegen funktioniert. So könnte per Graph z.B. ein Einladungsmanagement erfolgen

Kein Fehler

Per Graph kann ich mit einer Appication-Permission als jeder Benutzerpostfach, Raumpostfach, SharedMailbox ohne Einschränkung senden, solange dieser ein Exchange Postfach hat und ich eine der Mailadressen dieses Postfachs in der URL nutze. Alle anderen möglichen Absender wie Kontakte, RemoteMailbox, Verteiler, Microsoft Group, Microsoft Teams oder sogar beliebige andere Absenderadressen werden von vorneherein geblockt.

Erweiterte Funktionen

Wenn Sie die Beschreibung auf https://docs.microsoft.com/en-us/graph/api/user-sendmail lesen, dann sehen Sie, dass der JSON-Post einmal die Message selbst und ein Flag "saveToSentItems" enthält, welches "True" ist. Jede per Graph gesendete Nachricht landen also auch im Ordner "Gesendete Objekte".

Aber auch das "Message-Objekt" kann ich mit weiteren Properties anreichern. Schließlich muss ich es ja nicht bei einer nackten HTML-Mail belassen. Mails können ja auch Anlagen, CC und BCC-Empfänger und viele andere Properties haben. Alle Felder sind auf folgender Seite von Microsoft beschrieben.

Allerdings sind für den Versand einer Mail natürlicl nicht alle Felder nutzbar. Das "Message"-Objekt kommt ja auch zum Einsatz, wenn Sie eine Mail lesen. Da machen Felder wie "isRead" schon mehr Sinn.

Einschätzung

Das "Mail.Send"-Beispiel ist eine einfache Übung, mit der jemand einer Application umfangreiche Rechte geben und die Auswirkungen experimentell in einem Testfeld durchspielen kann. Als Gegenstelle zur Kontrolle eignet sich einfach Outlook. Allerdings sehe ich den Mehrwert als eher gering ein, denn für den Einsatz bei einem Anwender müsste die Consent-Gewährung erfolgen und der Anwender könnte die "AppID" und das Secret auslesen.

Ein Ersatz für "Send-Mailmessage" ist Graph daher nicht.

Weitere Links