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?

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

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