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
Sie gewähren einer App das Recht als beliebiger Benutzer eine Mail zu senden. Das ist dann quasi "Impersonation" für Mail senden
Dies beschreibt Microsoft auch auf der "Permission"-Seite:
https://docs.microsoft.com/en-us/graph/api/user-sendmail
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.
Geht das nur mit Postfachuser oder auch RemoteUser, Kontakt, Verteiler?
- Graph: Send mail
https://docs.microsoft.com/en-us/graph/api/user-sendmail - message resource type
https://docs.microsoft.com/en-us/graph/api/resources/message
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.
- 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
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
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-Adresses 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
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"-FallEin 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 BenutzerWenn 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-AdresseViele 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 AdresseDann 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 RaumDer 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.
- message resource type
https://docs.microsoft.com/en-us/graph/api/resources/message?view=graph-rest-1.0
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.
Scope mit ApplicationAccessPolicy
Mit der Gewährung einer "Application Permission" muss man der Anwendung schon sehr stark vertrauen, denn Sie kann sich als "jedes Exchange Online Postfach" in ihrem Tenant ausgeben. Das ist ein sehr mächtiges Missbrauchspotential. In Exchange können Sie eine Graph ApplicationAccessPolicy nutzen.
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
- Graph Token
- Graph ApplicationAccessPolicy
- MGGraph Mail
- Graph: Send mail
https://docs.microsoft.com/en-us/graph/api/user-sendmail - message resource type
https://docs.microsoft.com/en-us/graph/api/resources/message - Sending Emails Using Microsoft Graph
PowerShell
https://helloitsliam.com/2021/10/18/sending-emails-using-microsoft-graph-powershell/ - Sending e-mails with Microsoft Graph
using .NET
https://zimmergren.net/sending-e-mails-using-microsoft-graph-using-dotnet/