Mails programmatisch versenden mit der Microsoft Graph API
Warum Sie SMTP zugunsten der Microsoft Graph API aufgeben sollten: App-Registrierung, OAuth2 Client-Credentials-Flow und C#-Implementierung mit dem Graph SDK.
Wer in einer Microsoft-365-Umgebung automatisiert E-Mails versenden muss – sei es für Benachrichtigungen, Reports oder Workflow-Trigger – hat lange auf SMTP-AUTH mit Benutzername und Passwort gesetzt. Microsoft schränkt diesen Weg jedoch konsequent ein: Basic Authentication wurde für die meisten Protokolle (Outlook, EWS, POP, IMAP, EAS) bereits im Oktober 2022 deaktiviert. SMTP-AUTH war dabei zunächst die Ausnahme, doch auch hier endet die Unterstützung für Basic Authentication: Ab April 2026 ist für SMTP-AUTH OAuth verpflichtend, und für bestehende Tenants wird Basic Auth bis Ende 2026 standardmäßig deaktiviert. Neue Tenants erhalten SMTP-AUTH mit Basic Auth gar nicht mehr. Die empfohlene Alternative ist die Microsoft Graph API, die auf OAuth2 basiert und zentral über Microsoft Entra ID gesteuert wird.
Dieser Artikel zeigt den vollständigen Weg: von der App-Registrierung über den Token-Abruf bis zum funktionierenden C#-Code und einem Raw-HTTP-Beispiel per curl.
Warum Graph statt SMTP?
SMTP-AUTH erfordert dauerhaft gespeicherte Anmeldedaten eines Benutzers oder eines Service-Accounts. Das bringt mehrere Nachteile mit sich:
- Basic Auth läuft aus. Für die meisten Exchange-Online-Protokolle ist Basic Authentication bereits deaktiviert; bei SMTP-AUTH folgt die Abschaltung 2026. Wer heute neu baut, sollte gar nicht erst auf Basic Auth setzen.
- Keine Granularität. Mit einem SMTP-Credential hat die Anwendung Vollzugriff auf das Postfach – inklusive Lesen, Löschen und Verschieben.
- Kein zentrales Audit. Token-basierte Authentifizierung über Entra ID liefert strukturierte Sign-in-Logs und lässt sich über Conditional Access absichern. SMTP-AUTH ist demgegenüber schwer zu steuern und zu auditieren.
- Graph ist API-first. Derselbe Flow, der Mails versendet, kann über dieselbe App-Registrierung auch Kalender, Teams-Nachrichten oder SharePoint ansprechen.
Mit der Graph API authentifiziert sich die Anwendung als eigene Identität (Anwendungsberechtigung), nicht als Benutzer. Das ermöglicht eine klare Trennung von Mensch und Maschine.
App-Registrierung in Microsoft Entra ID
Schritt 1: App anlegen und Berechtigung vergeben
Navigieren Sie im Microsoft Entra Admin Center zu App-Registrierungen und legen Sie eine neue Anwendung an. Unter API-Berechtigungen fügen Sie folgende Berechtigung hinzu:
| Typ | API | Berechtigung |
|---|---|---|
| Anwendungsberechtigung | Microsoft Graph | Mail.Send |
Anschließend muss ein globaler Administrator Admin-Consent erteilen. Ohne Consent ist die Berechtigung zwar vorhanden, aber nicht aktiv. Der Unterschied zwischen delegierter Berechtigung und Anwendungsberechtigung ist hier entscheidend: Eine delegierte Berechtigung handelt im Namen eines angemeldeten Benutzers, eine Anwendungsberechtigung handelt im Namen der App selbst – und kann damit ohne weitere Einschränkung auf alle Postfächer des Tenants zugreifen.
Schritt 2: Zugriff einschränken
Mail.Send als Anwendungsberechtigung erlaubt es der App standardmäßig, E-Mails aus jedem Postfach des Tenants zu senden. Das ist ein erhebliches Risiko, das Sie unbedingt eingrenzen sollten.
Microsoft bietet hierfür zwei Mechanismen an:
- RBAC for Applications – der von Microsoft empfohlene, zukunftssichere Weg. Er bindet die App über ihren Service Principal an eine Rolle (z. B.
Mail.Send) und einen Management-Scope, der die zugänglichen Postfächer festlegt. - Application Access Policy – der ältere, etablierte Mechanismus. Microsoft hat ihn inzwischen als Legacy gekennzeichnet und empfiehlt für Neuimplementierungen RBAC for Applications. Für bestehende Umgebungen und als schnell eingerichteter Einstieg ist die Application Access Policy weiterhin funktionsfähig.
Die Application Access Policy beschränkt den Zugriff auf die Mitglieder einer Mail-Enabled Security Group. Die Konfiguration erfolgt per Exchange Online PowerShell:
# Verbindung herstellen
Connect-ExchangeOnline
# Policy anlegen: Zugriff nur auf Mitglieder der Gruppe erlauben
New-ApplicationAccessPolicy `
-AppId "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" `
-PolicyScopeGroupId "noreply-senders@contoso.com" `
-AccessRight RestrictAccess `
-Description "Nur Versand aus dedizierten Service-Postfaechern"
# Policy testen
Test-ApplicationAccessPolicy `
-Identity "service-mailer@contoso.com" `
-AppId "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
PolicyScopeGroupId erwartet eine Mail-Enabled Security Group (z. B. deren E-Mail-Adresse). So sendet die App ausschließlich aus Postfächern, die Mitglied der angegebenen Gruppe sind. Least Privilege ist damit auch auf Tenant-Ebene durchgesetzt.
Schritt 3: Client-Secret oder Zertifikat
Für die App-Authentifizierung benötigen Sie entweder ein Client-Secret (einfach, aber ein rotierendes Shared Secret) oder ein Zertifikat (empfohlen für Produktion). Ein Zertifikat lässt sich in Azure Key Vault speichern und per Managed Identity in der Laufzeitumgebung abrufen – kein Secret landet je im Code oder in einer Konfigurationsdatei.
Für den Einstieg zeigen die folgenden Beispiele den Secret-basierten Flow. In Produktion ersetzen Sie ClientSecretCredential durch ClientCertificateCredential aus dem Paket Azure.Identity.
OAuth2 Client-Credentials-Flow
Der Ablauf lässt sich in vier Schritten darstellen:
(1) POST /oauth2/v2.0/token
client_id, client_secret, scope, grant_type
+-----------------+ -------------------------------> +-----------------+
| | | |
| Ihre App | | Entra ID |
| (Daemon) | | Token-Endpoint |
| | <------------------------------- | |
+-----------------+ (2) Access Token (JWT) +-----------------+
|
| (3) POST /v1.0/users/{upn}/sendMail
| Authorization: Bearer <token>
v
+-----------------+ +-----------------+
| | -------------------------------> | |
| Graph API | JSON Mail-Payload | Exchange Online |
| /sendMail | | Postfach |
| | <------------------------------- | |
+-----------------+ (4) HTTP 202 Accepted +-----------------+
Der Token-Endpoint hat folgendes Format:
https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token
Der Scope für Graph-API-Anwendungsberechtigungen ist immer:
https://graph.microsoft.com/.default
Implementierung in C# mit dem Microsoft Graph SDK
Installieren Sie zunächst die benötigten Pakete:
dotnet add package Microsoft.Graph
dotnet add package Azure.Identity
Dann der eigentliche Code – vollständig typisiert mit einem record-Typ für die Konfiguration:
using Azure.Identity;
using Microsoft.Graph;
using Microsoft.Graph.Models;
using Microsoft.Graph.Users.Item.SendMail;
// Konfiguration – in Produktion aus Azure App Configuration / Key Vault laden
var config = new GraphMailConfig(
TenantId: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
ClientId: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
ClientSecret: Environment.GetEnvironmentVariable("GRAPH_CLIENT_SECRET")!,
SenderUpn: "service-mailer@contoso.com"
);
var credential = new ClientSecretCredential(
config.TenantId,
config.ClientId,
config.ClientSecret
);
var graphClient = new GraphServiceClient(credential);
var requestBody = new SendMailPostRequestBody
{
Message = new Message
{
Subject = "Automatisierte Benachrichtigung",
Body = new ItemBody
{
ContentType = BodyType.Html,
Content = "<h1>Hallo</h1><p>Dies ist eine automatisierte Nachricht.</p>"
},
ToRecipients = new List<Recipient>
{
new Recipient
{
EmailAddress = new EmailAddress
{
Address = "empfaenger@beispiel.de"
}
}
}
},
SaveToSentItems = false // Sent-Items des Service-Accounts nicht befuellen
};
await graphClient
.Users[config.SenderUpn]
.SendMail
.PostAsync(requestBody);
Console.WriteLine("Mail erfolgreich gesendet.");
// Konfigurationsrecord
record GraphMailConfig(
string TenantId,
string ClientId,
string ClientSecret,
string SenderUpn
);
SaveToSentItems = false ist bei Service-Accounts in der Regel sinnvoll, um das Postfach nicht unnötig zu befüllen. Der Standardwert der Graph API ist true; setzen Sie den Wert nur dann ausdrücklich, wenn Sie das Verhalten ändern möchten – etwa auf true, wenn Sie eine Sendehistorie im Postfach benötigen.
Raw-HTTP-Variante mit curl
Wer den Flow ohne SDK verstehen oder in einer anderen Sprache nachbauen möchte, kommt mit zwei curl-Aufrufen ans Ziel.
Schritt 1: Token holen
TOKEN=$(curl -s -X POST \
"https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=${CLIENT_ID}" \
-d "client_secret=${CLIENT_SECRET}" \
-d "scope=https://graph.microsoft.com/.default" \
-d "grant_type=client_credentials" \
| jq -r '.access_token')
Schritt 2: Mail senden
curl -s -X POST \
"https://graph.microsoft.com/v1.0/users/service-mailer@contoso.com/sendMail" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"message": {
"subject": "Test via Graph API",
"body": {
"contentType": "Text",
"content": "Hallo aus dem Terminal."
},
"toRecipients": [
{
"emailAddress": {
"address": "empfaenger@beispiel.de"
}
}
]
},
"saveToSentItems": false
}'
Ein HTTP-Status 202 Accepted bedeutet, dass Exchange Online die Mail zur Zustellung angenommen hat. Achtung: Das ist keine Zustellbestätigung. Die eigentliche Verarbeitung läuft asynchron im Hintergrund und unterliegt den Limits und dem Throttling von Exchange Online – das entspricht dem normalen SMTP-Verhalten.
Sicherheitshinweise kompakt
- Zertifikat statt Secret. Client-Secrets laufen ab und müssen rotiert werden. Ein in Azure Key Vault gespeichertes Zertifikat lässt sich per Managed Identity abrufen, ohne dass ein Secret in der Anwendungskonfiguration landet.
- Postfach-Scope erzwingen. Ohne Einschränkung hat Ihre App Zugriff auf alle Postfächer. Begrenzen Sie den Zugriff – bevorzugt über RBAC for Applications, alternativ über eine Application Access Policy – auf ein dediziertes Service-Postfach.
- Secrets nie im Code. Verwenden Sie
Azure.Extensions.AspNetCore.Configuration.SecretsoderIConfigurationmit Key-Vault-Binding.Environment.GetEnvironmentVariableist für lokale Entwicklung akzeptabel, nicht für Produktion. - Token-Caching.
GraphServiceClientmitAzure.Identitycached Tokens automatisch. Rufen Sie nicht bei jeder Mail einen neuen Token ab.
Praxis-Empfehlung
Der beschriebene Ansatz – App-Registrierung mit Mail.Send, eingeschränkt auf ein dediziertes Service-Postfach (per RBAC for Applications oder Application Access Policy), authentifiziert per Zertifikat aus Azure Key Vault – ist der sicherste und am einfachsten auditierbare Weg, in einer Microsoft-365-Umgebung automatisiert Mails zu versenden. Der Mehraufwand gegenüber SMTP ist einmalig und gering; der Gewinn in Governance, Sicherheit und Wartbarkeit ist dauerhaft erheblich.
Haben Sie Fragen zur Implementierung, zur Zertifikatsstrategie oder zur Integration in Ihre bestehende .NET-Architektur? Schreiben Sie uns gerne unter info@yurtbay.dev.