.NET MAUI: Senden einer eMail
Lesedauer: 4 Minuten

Es gibt immer mal wieder das Szenario, dass man eine Mail aus einer App heraus senden möchte. Dies ist zum Beispiel der Fall, wenn man dem Nutzer eine Möglichkeit bieten möchte, dem Entwickler einer Nachricht zu hinterlassen. Ebenso ist das Szenario für ein Export einsetzbar. In diesem Blog-Beitrag möchte ich jetzt aufzeigen, wie man mit wenig Aufwand eine eMail-Nachricht aus einer .NET MAUI App versenden kann.

Zunächst legen wir uns ein neues .NET MAUI App-Projekt in Visual Studio 2022 an.

Im nächsten Schritt installieren wir das CommunityToolkit.Mvvm NuGet-Package. Dieses ermöglicht uns die Verwendung von MVVM innerhalb unserer App und spart eine Menge an Boilerplate-Code ein. Ich habe zu diesem Package auch schon ein Video auf meinem YouTube-Kanal veröffentlicht.

Nun können wir uns bereits an die Entwicklung machen. Glücklicherweise stellt Maui.Essentials eine Options bereit den internen Mail-Client zu öffnen. Hierfür sind für die verschiedenen Plattformen jedoch Vorbereitungen zu treffen. Unter Android müsst ihr die Datei AndroidManifest.xml öffnen, welche ihr im Ordner Platforms/Android findet. Hier ergänzt ihr am Ende der Datei den folgenden queries/intent-Knoten.

<queries>
  <intent>
    <action android:name="android.intent.action.SENDTO" />
    <data android:scheme="mailto" />
  </intent>
</queries>

Auch für iOS bzw. Mac Catalyst sind Anpassungen notwendig. Öffnet hierfür die Datei Info.plist, welche sich einmal im Ordner Platforms/iOS und einmal im Ordner Platforms/MacCatalyst befindet und ergänzt den folgenden Eintrag.

<key>LSApplicationQueriesSchemes</key>
<array>
  <string>mailto</string>
</array>

Wir erstellen uns nun einen Ordner ViewModels und darin eine Klasse BaseViewModel. Diese Klasse markieren wir mit dem INotifyPropertyChanged-Attribut und machen die Klasse partial. Anschließend fügen wir die ObservableProperty _isLoading hinzu.

[INotifyPropertyChanged]
public partial class BaseViewModel
{
    [ObservableProperty]
    private bool _isLoading;
}

Im nächsten Schritt erstellen wir ein neues ViewModel im ViewModels-Ordner mit dem Namen MailViewModel. Dieses hat als Basisklasse natürlich das gerade erstellte BaseViewModel und auch hier markieren wir die Klasse wieder mit dem partial Schlüsselwort. Über den Konstruktor lassen wir uns das Interface IEmail injecten und speichern dieses als _emailService in einer Variablen ab. Anschließend erstellen wir die Methode OpenMailClientAsync, welche wir mit dem RelayCommand-Attribut ausstatten, so dass wir diese Methode per Command aufrufen können. Innerhalb der Methode erstellen wir uns nun eine neue EmailMessage und belegen einige Dinge, wie Betreff, Body und den Empfänger fest. Anschließend rufen wir ComposeAsync auf, welche den Standard-Mailclient öffnet.

public partial class MailViewModel : BaseViewModel
{
    private readonly IEmail _emailService;

    private const string _subject = "My Subject";
    private const string _body = "The Mail Body";
    private const string _recipient = "<ENTER THE RECIPIENT HERE>";

    public MailViewModel(IEmail emailService)
    {
        _emailService = emailService;
    }

    [RelayCommand(AllowConcurrentExecutions = false)]
    private async Task OpenMailClientAsync()
    {
        try
        {
            IsLoading = true;

            if (_emailService.IsComposeSupported)
            {
                var emailMessage = new EmailMessage
                {
                    Subject = _subject,
                    Body = _body,
                    To = new List<string> { _recipient }
                };

                await _emailService.ComposeAsync(emailMessage);
            }
            else
            {
                await Application.Current.MainPage.DisplayAlert("Unsupported", 
                   "The opening of the email client is currently not supported.", "OK");
            }
        }
        catch (Exception ex)
        {
            await Application.Current.MainPage.DisplayAlert("Error", ex.ToString(), "OK");
        }
        finally
        {
            IsLoading = false;
        }
    }
}

Nun erstellen wir uns noch einen Views-Ordner und darin eine ContentPage mit dem Namen MailPage. Hier fügen wir nun den Namespace zu unseren ViewModels hinzu und setzen den DataType auf unser MailViewModel, um Code-Vervollständigung zu erhalten. In einem VerticalStackLayout fügen wir nun einen Button hinzu und setzen OpenMailClientCommand als Command.

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:viewmodels="clr-namespace:MauiMail.ViewModels"
             x:Class="MauiMail.Views.MailPage"
             x:DataType="viewmodels:MailViewModel"
             Title="Mail"
             Padding="16">

    <VerticalStackLayout Spacing="8">
        <Button Text="Open Mail Client"
                Command="{Binding OpenMailClientCommand}" />

        <ActivityIndicator WidthRequest="50"
                           HeightRequest="50"
                           HorizontalOptions="Center"
                           IsVisible="{Binding IsLoading}"
                           IsRunning="{Binding IsLoading}" />

    </VerticalStackLayout>
</ContentPage>

In der Code-Behind-Datei lassen wir uns jetzt das MailViewModel noch injecten und setzen dann den BindingContext für unsere Page.

public partial class MailPage : ContentPage
{
    public MailPage(MailViewModel mailViewModel)
    {
        InitializeComponent();

        BindingContext = mailViewModel;
    }
}

Bevor wir unsere App jetzt einmal ausprobieren können, müssen wir noch die Page, das ViewModel und auch das IEmail-Interface registrieren.

public static class MauiProgram
{
	public static MauiApp CreateMauiApp()
	{
		var builder = MauiApp.CreateBuilder();
		builder
			.UseMauiApp<App>()
			.ConfigureFonts(fonts =>
			{
				fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
				fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
			});

		// Views
		builder.Services.AddTransient<MailPage>();

		// Services
		builder.Services.AddSingleton(Email.Default);

		// ViewModels
		builder.Services.AddTransient<MailViewModel>();

		return builder.Build();
	}
}

In der AppShell tauschen wir nun noch die MainPage gegen die MailPage aus und anschließend können wir die App starten.

<?xml version="1.0" encoding="UTF-8" ?>
<Shell x:Class="MauiMail.AppShell"
       xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
       xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
       xmlns:views="clr-namespace:MauiMail.Views"
       Shell.FlyoutBehavior="Disabled">

    <ShellContent Title="Mail"
                  ContentTemplate="{DataTemplate views:MailPage}"
                  Route="MailPage" />

</Shell>

Die folgenden Screenshots zeigen das Verhalten der App unter Android.

Schauen wir uns nun einmal die Windows-Version an. Nach dem Klicken auf den Button sehen wir leider einen Dialog mit einer Fehlermeldung.

Ein wenig Recherche ergibt direkt, dass ein Problem mit dem Senden von eMail-Nachrichten über das IEmail-Interface unter Windows gibt. Durch den Wechsel von UWP auf WinUI stehen leider nicht alle Dinge zur Verfügung. Dazu zählt auch das Öffnen des Mail-Clients. Weitere Informationen könnt ihr dem GitHub-Issue entnehmen.

Nun müssen wir uns eine Alternative überlegen. Glücklicherweise gibt es die Klasse SmtpClient. Diese ermöglicht das direkte Senden einer eMail-Nachricht, so dass sich nicht erst noch der Email-Client öffnet. Dafür sind ein paar weitere Informationen, wie der SMTP Host oder der SMTP Port, sowie das Passwort für die Email-Adresse.

private const string _subject = "My Subject";
private const string _body = "The Mail Body";
private const string _recipient = "<ENTER THE RECIPIENT HERE>";
private const string _from = "<ENTER THE FROM ADDRESS HERE>";
private const string _smtpHost = "<ADD SMTP HOST>";
private const int _smtpPort = 587; // Update with the corresponding SMTP Port
private NetworkCredential _smtpCredentials = new("<MAIL ADDRESS>", "<MAIL PASSWORD>");

[RelayCommand(AllowConcurrentExecutions = false)]
private async Task SendMailViaSmtpClientAsync()
{
    try
    {
        IsLoading = true;

        var smtpClient = new SmtpClient(_smtpHost)
        {
            Port = _smtpPort,
            Credentials = _smtpCredentials,
            EnableSsl = true,
        };

        var mailMessage = new MailMessage
        {
            From = new MailAddress(_from),
            Subject = _subject,
            Body = _body
        };
        mailMessage.To.Add(_recipient);

        await smtpClient.SendMailAsync(mailMessage);

        await Application.Current.MainPage.DisplayAlert("Success", 
            "Message was sent.", "Ok");
    }
    catch (Exception ex)
    {
        await Application.Current.MainPage.DisplayAlert("Error", ex.ToString(), "OK");
    }
    finally
    {
        IsLoading = false;
    }
}

Wir fügen nun noch einen weiteren Button auf der MailPage hinzu und können uns die App nun einmal anschauen.

<Button Text="Send Mail via SmtpClient"
        Command="{Binding SendMailViaSmtpClientCommand}" />

Hier nun die App unter Windows. Wie ihr sehen könnt, wurde die Nachricht nun versendet.

So haben wir zumindest einen Workaround gefunden, dass wir eine Email senden können. Aber Microsoft arbeitet bereits an der Anpassung, so dass das IEmail-Interface auch unter WinUI demnächst funktionieren könnte.

Buch-Tipp: Cross-Plattform-Apps mit .NET MAUI entwickeln von André Krämer Von Xamarin.Forms zu .NET MAUI BindableProperty-Generator: Code bei Bindable Properties vereinfachen