diff --git a/Exceptionless.Net.slnx b/Exceptionless.Net.slnx
index c177c837..81f0b918 100644
--- a/Exceptionless.Net.slnx
+++ b/Exceptionless.Net.slnx
@@ -19,6 +19,7 @@
+
diff --git a/samples/Exceptionless.SampleMaui/App.cs b/samples/Exceptionless.SampleMaui/App.cs
new file mode 100644
index 00000000..00acd6ba
--- /dev/null
+++ b/samples/Exceptionless.SampleMaui/App.cs
@@ -0,0 +1,20 @@
+namespace Exceptionless.SampleMaui;
+
+public sealed class App : Application {
+ private readonly ExceptionlessClient _exceptionlessClient;
+ private readonly MainPage _mainPage;
+
+ public App(MainPage mainPage, ExceptionlessClient exceptionlessClient) {
+ _exceptionlessClient = exceptionlessClient;
+ _mainPage = mainPage;
+ }
+
+ protected override Window CreateWindow(IActivationState? activationState) {
+ return new Window(_mainPage);
+ }
+
+ protected override void OnSleep() {
+ _ = _exceptionlessClient.ProcessQueueAsync();
+ base.OnSleep();
+ }
+}
diff --git a/samples/Exceptionless.SampleMaui/Exceptionless.SampleMaui.csproj b/samples/Exceptionless.SampleMaui/Exceptionless.SampleMaui.csproj
new file mode 100644
index 00000000..873fffc3
--- /dev/null
+++ b/samples/Exceptionless.SampleMaui/Exceptionless.SampleMaui.csproj
@@ -0,0 +1,40 @@
+
+
+
+ net10.0-android
+ $(TargetFrameworks);net10.0-ios;net10.0-maccatalyst
+ $(TargetFrameworks);net10.0-windows10.0.19041.0
+ Exe
+ Exceptionless.SampleMaui
+ true
+ true
+ enable
+ enable
+
+ Exceptionless MAUI Sample
+ com.exceptionless.samplemaui
+ 1.0
+ 1
+ None
+
+ 21.0
+ 15.0
+ 15.0
+ 10.0.17763.0
+ 10.0.17763.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/Exceptionless.SampleMaui/MainPage.cs b/samples/Exceptionless.SampleMaui/MainPage.cs
new file mode 100644
index 00000000..79a70f3b
--- /dev/null
+++ b/samples/Exceptionless.SampleMaui/MainPage.cs
@@ -0,0 +1,193 @@
+using Exceptionless.Logging;
+using Microsoft.Maui.Controls.Shapes;
+
+namespace Exceptionless.SampleMaui;
+
+public sealed class MainPage : ContentPage {
+ private readonly ExceptionlessClient _exceptionlessClient;
+ private readonly Label _statusLabel;
+ private readonly Label _lastReferenceIdLabel;
+ private readonly ActivityIndicator _activityIndicator;
+
+ public MainPage(ExceptionlessClient exceptionlessClient) {
+ _exceptionlessClient = exceptionlessClient;
+
+ Title = "Exceptionless";
+ BackgroundColor = Color.FromArgb("#F6F8FA");
+
+ _statusLabel = new Label {
+ Text = "Ready",
+ FontSize = 14,
+ TextColor = Color.FromArgb("#314256"),
+ LineBreakMode = LineBreakMode.WordWrap
+ };
+
+ _lastReferenceIdLabel = new Label {
+ Text = "Last reference id: none",
+ FontSize = 13,
+ TextColor = Color.FromArgb("#576575"),
+ LineBreakMode = LineBreakMode.TailTruncation
+ };
+
+ _activityIndicator = new ActivityIndicator {
+ IsVisible = false,
+ Color = Color.FromArgb("#276749")
+ };
+
+ Content = BuildContent();
+ }
+
+ private View BuildContent() {
+ var sendExceptionButton = CreateActionButton("Send Handled Exception", OnSendExceptionClicked);
+ var sendLogButton = CreateActionButton("Send Warning Log", OnSendLogClicked);
+ var trackFeatureButton = CreateActionButton("Track Feature", OnTrackFeatureClicked);
+ var flushButton = CreateActionButton("Flush Queue", OnFlushClicked);
+
+ return new ScrollView {
+ Content = new VerticalStackLayout {
+ Padding = new Thickness(24, 28),
+ Spacing = 18,
+ Children = {
+ new Label {
+ Text = "Exceptionless MAUI Sample",
+ FontSize = 26,
+ FontAttributes = FontAttributes.Bold,
+ TextColor = Color.FromArgb("#1D2733")
+ },
+ new Label {
+ Text = "Submit sample events through the core Exceptionless client.",
+ FontSize = 15,
+ TextColor = Color.FromArgb("#576575"),
+ LineBreakMode = LineBreakMode.WordWrap
+ },
+ new Border {
+ Stroke = Color.FromArgb("#D8DEE6"),
+ StrokeThickness = 1,
+ BackgroundColor = Colors.White,
+ StrokeShape = new RoundRectangle { CornerRadius = 8 },
+ Padding = new Thickness(18),
+ Content = new VerticalStackLayout {
+ Spacing = 12,
+ Children = {
+ new Label {
+ Text = "Client",
+ FontSize = 18,
+ FontAttributes = FontAttributes.Bold,
+ TextColor = Color.FromArgb("#1D2733")
+ },
+ new Label {
+ Text = $"Server: {_exceptionlessClient.Configuration.ServerUrl}",
+ FontSize = 13,
+ TextColor = Color.FromArgb("#576575"),
+ LineBreakMode = LineBreakMode.TailTruncation
+ },
+ new Label {
+ Text = $"Private information: {_exceptionlessClient.Configuration.IncludePrivateInformation}",
+ FontSize = 13,
+ TextColor = Color.FromArgb("#576575")
+ },
+ _statusLabel,
+ _lastReferenceIdLabel,
+ _activityIndicator
+ }
+ }
+ },
+ new Grid {
+ ColumnDefinitions = {
+ new ColumnDefinition { Width = GridLength.Star },
+ new ColumnDefinition { Width = GridLength.Star }
+ },
+ RowDefinitions = {
+ new RowDefinition { Height = GridLength.Auto },
+ new RowDefinition { Height = GridLength.Auto }
+ },
+ ColumnSpacing = 12,
+ RowSpacing = 12,
+ Children = {
+ sendExceptionButton,
+ sendLogButton,
+ trackFeatureButton,
+ flushButton
+ }
+ }
+ }
+ }
+ };
+ }
+
+ private static Button CreateActionButton(string text, EventHandler clicked) {
+ var button = new Button {
+ Text = text,
+ BackgroundColor = Color.FromArgb("#285A84"),
+ TextColor = Colors.White,
+ CornerRadius = 8,
+ FontAttributes = FontAttributes.Bold,
+ MinimumHeightRequest = 48
+ };
+
+ button.Clicked += clicked;
+ return button;
+ }
+
+ private async void OnSendExceptionClicked(object? sender, EventArgs e) {
+ await RunClientActionAsync("Handled exception queued.", () => {
+ string referenceId = Guid.NewGuid().ToString("N");
+
+ try {
+ throw new InvalidOperationException("Exceptionless MAUI sample handled exception.");
+ } catch (Exception ex) {
+ ex.ToExceptionless(_exceptionlessClient)
+ .SetReferenceId(referenceId)
+ .AddTags("handled")
+ .SetProperty("Screen", nameof(MainPage))
+ .Submit();
+ }
+
+ SetLastReferenceId(referenceId);
+ return Task.CompletedTask;
+ });
+ }
+
+ private async void OnSendLogClicked(object? sender, EventArgs e) {
+ await RunClientActionAsync("Warning log queued.", () => {
+ _exceptionlessClient.SubmitLog("Exceptionless.SampleMaui.MainPage", "MAUI sample warning log.", LogLevel.Warn);
+ SetLastReferenceId(_exceptionlessClient.GetLastReferenceId());
+ return Task.CompletedTask;
+ });
+ }
+
+ private async void OnTrackFeatureClicked(object? sender, EventArgs e) {
+ await RunClientActionAsync("Feature usage queued.", () => {
+ _exceptionlessClient.SubmitFeatureUsage("MauiSample.TrackFeature");
+ SetLastReferenceId(_exceptionlessClient.GetLastReferenceId());
+ return Task.CompletedTask;
+ });
+ }
+
+ private async void OnFlushClicked(object? sender, EventArgs e) {
+ await RunClientActionAsync("Queue processed.", () => _exceptionlessClient.ProcessQueueAsync());
+ }
+
+ private async Task RunClientActionAsync(string successMessage, Func action) {
+ try {
+ _activityIndicator.IsVisible = true;
+ _activityIndicator.IsRunning = true;
+ _statusLabel.Text = "Working...";
+
+ await action();
+
+ _statusLabel.Text = successMessage;
+ } catch (Exception ex) {
+ _statusLabel.Text = $"Error: {ex.Message}";
+ } finally {
+ _activityIndicator.IsRunning = false;
+ _activityIndicator.IsVisible = false;
+ }
+ }
+
+ private void SetLastReferenceId(string? referenceId) {
+ _lastReferenceIdLabel.Text = String.IsNullOrEmpty(referenceId)
+ ? "Last reference id: none"
+ : $"Last reference id: {referenceId}";
+ }
+}
diff --git a/samples/Exceptionless.SampleMaui/MauiProgram.cs b/samples/Exceptionless.SampleMaui/MauiProgram.cs
new file mode 100644
index 00000000..52dc2669
--- /dev/null
+++ b/samples/Exceptionless.SampleMaui/MauiProgram.cs
@@ -0,0 +1,43 @@
+using Exceptionless.Logging;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Maui.Storage;
+
+namespace Exceptionless.SampleMaui;
+
+public static class MauiProgram {
+ private const string DefaultApiKey = "LhhP1C9gijpSKCslHHCvwdSIz298twx271nTest";
+ private const string DefaultServerUrl = "https://ex.dev.localhost:7111";
+
+ public static MauiApp CreateMauiApp() {
+ var builder = MauiApp.CreateBuilder();
+ var exceptionlessClient = CreateExceptionlessClient();
+
+ builder
+ .UseMauiApp();
+
+ builder.Services.AddSingleton(exceptionlessClient);
+ builder.Services.AddSingleton();
+
+ return builder.Build();
+ }
+
+ private static ExceptionlessClient CreateExceptionlessClient() {
+ string appDataDirectory = FileSystem.Current.AppDataDirectory;
+
+ var client = new ExceptionlessClient(config => {
+ config.ApiKey = Environment.GetEnvironmentVariable("EXCEPTIONLESS_API_KEY") ?? DefaultApiKey;
+ config.ServerUrl = Environment.GetEnvironmentVariable("EXCEPTIONLESS_SERVER_URL") ?? DefaultServerUrl;
+ config.IncludePrivateInformation = false;
+ config.DefaultTags.Add("maui");
+ config.DefaultTags.Add("sample");
+ config.DefaultData["Platform"] = DeviceInfo.Current.Platform.ToString();
+ config.DefaultData["DeviceIdiom"] = DeviceInfo.Current.Idiom.ToString();
+ config.SetVersion(AppInfo.Current.VersionString);
+ config.UseFolderStorage(Path.Combine(appDataDirectory, "exceptionless-queue"));
+ config.UseFileLogger(Path.Combine(appDataDirectory, "exceptionless-client.log"), LogLevel.Info);
+ });
+
+ client.Startup();
+ return client;
+ }
+}
diff --git a/samples/Exceptionless.SampleMaui/Platforms/Android/AndroidManifest.xml b/samples/Exceptionless.SampleMaui/Platforms/Android/AndroidManifest.xml
new file mode 100644
index 00000000..29509345
--- /dev/null
+++ b/samples/Exceptionless.SampleMaui/Platforms/Android/AndroidManifest.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/samples/Exceptionless.SampleMaui/Platforms/Android/MainActivity.cs b/samples/Exceptionless.SampleMaui/Platforms/Android/MainActivity.cs
new file mode 100644
index 00000000..6542cf21
--- /dev/null
+++ b/samples/Exceptionless.SampleMaui/Platforms/Android/MainActivity.cs
@@ -0,0 +1,8 @@
+using Android.App;
+using Android.Content.PM;
+
+namespace Exceptionless.SampleMaui;
+
+[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
+public class MainActivity : MauiAppCompatActivity {
+}
diff --git a/samples/Exceptionless.SampleMaui/Platforms/Android/MainApplication.cs b/samples/Exceptionless.SampleMaui/Platforms/Android/MainApplication.cs
new file mode 100644
index 00000000..3c4e0847
--- /dev/null
+++ b/samples/Exceptionless.SampleMaui/Platforms/Android/MainApplication.cs
@@ -0,0 +1,12 @@
+using Android.App;
+using Android.Runtime;
+
+namespace Exceptionless.SampleMaui;
+
+[Application]
+public class MainApplication : MauiApplication {
+ public MainApplication(IntPtr handle, JniHandleOwnership ownership) : base(handle, ownership) {
+ }
+
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+}
diff --git a/samples/Exceptionless.SampleMaui/Platforms/MacCatalyst/AppDelegate.cs b/samples/Exceptionless.SampleMaui/Platforms/MacCatalyst/AppDelegate.cs
new file mode 100644
index 00000000..103a1cef
--- /dev/null
+++ b/samples/Exceptionless.SampleMaui/Platforms/MacCatalyst/AppDelegate.cs
@@ -0,0 +1,8 @@
+using Foundation;
+
+namespace Exceptionless.SampleMaui;
+
+[Register("AppDelegate")]
+public class AppDelegate : MauiUIApplicationDelegate {
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+}
diff --git a/samples/Exceptionless.SampleMaui/Platforms/MacCatalyst/Entitlements.plist b/samples/Exceptionless.SampleMaui/Platforms/MacCatalyst/Entitlements.plist
new file mode 100644
index 00000000..a8c10726
--- /dev/null
+++ b/samples/Exceptionless.SampleMaui/Platforms/MacCatalyst/Entitlements.plist
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.security.app-sandbox
+
+ com.apple.security.network.client
+
+
+
diff --git a/samples/Exceptionless.SampleMaui/Platforms/MacCatalyst/Info.plist b/samples/Exceptionless.SampleMaui/Platforms/MacCatalyst/Info.plist
new file mode 100644
index 00000000..ab9e1df4
--- /dev/null
+++ b/samples/Exceptionless.SampleMaui/Platforms/MacCatalyst/Info.plist
@@ -0,0 +1,31 @@
+
+
+
+
+ UIDeviceFamily
+
+ 2
+
+ LSApplicationCategoryType
+ public.app-category.developer-tools
+ UIRequiredDeviceCapabilities
+
+ arm64
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ XSAppIconAssets
+ Assets.xcassets/appicon.appiconset
+
+
diff --git a/samples/Exceptionless.SampleMaui/Platforms/MacCatalyst/Program.cs b/samples/Exceptionless.SampleMaui/Platforms/MacCatalyst/Program.cs
new file mode 100644
index 00000000..7d22b9f6
--- /dev/null
+++ b/samples/Exceptionless.SampleMaui/Platforms/MacCatalyst/Program.cs
@@ -0,0 +1,9 @@
+using UIKit;
+
+namespace Exceptionless.SampleMaui;
+
+public static class Program {
+ private static void Main(string[] args) {
+ UIApplication.Main(args, null, typeof(AppDelegate));
+ }
+}
diff --git a/samples/Exceptionless.SampleMaui/Platforms/Windows/App.xaml b/samples/Exceptionless.SampleMaui/Platforms/Windows/App.xaml
new file mode 100644
index 00000000..36a665ef
--- /dev/null
+++ b/samples/Exceptionless.SampleMaui/Platforms/Windows/App.xaml
@@ -0,0 +1,7 @@
+
+
diff --git a/samples/Exceptionless.SampleMaui/Platforms/Windows/App.xaml.cs b/samples/Exceptionless.SampleMaui/Platforms/Windows/App.xaml.cs
new file mode 100644
index 00000000..71d4791f
--- /dev/null
+++ b/samples/Exceptionless.SampleMaui/Platforms/Windows/App.xaml.cs
@@ -0,0 +1,11 @@
+using Microsoft.UI.Xaml;
+
+namespace Exceptionless.SampleMaui.WinUI;
+
+public partial class App : MauiWinUIApplication {
+ public App() {
+ InitializeComponent();
+ }
+
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+}
diff --git a/samples/Exceptionless.SampleMaui/Platforms/Windows/Package.appxmanifest b/samples/Exceptionless.SampleMaui/Platforms/Windows/Package.appxmanifest
new file mode 100644
index 00000000..cfbe3da1
--- /dev/null
+++ b/samples/Exceptionless.SampleMaui/Platforms/Windows/Package.appxmanifest
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+ Exceptionless MAUI Sample
+ Exceptionless
+ appiconStoreLogo.png
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/Exceptionless.SampleMaui/Platforms/Windows/app.manifest b/samples/Exceptionless.SampleMaui/Platforms/Windows/app.manifest
new file mode 100644
index 00000000..b04556c6
--- /dev/null
+++ b/samples/Exceptionless.SampleMaui/Platforms/Windows/app.manifest
@@ -0,0 +1,11 @@
+
+
+
+
+
+ true/PM
+ PerMonitorV2, PerMonitor
+ true
+
+
+
diff --git a/samples/Exceptionless.SampleMaui/Platforms/iOS/AppDelegate.cs b/samples/Exceptionless.SampleMaui/Platforms/iOS/AppDelegate.cs
new file mode 100644
index 00000000..103a1cef
--- /dev/null
+++ b/samples/Exceptionless.SampleMaui/Platforms/iOS/AppDelegate.cs
@@ -0,0 +1,8 @@
+using Foundation;
+
+namespace Exceptionless.SampleMaui;
+
+[Register("AppDelegate")]
+public class AppDelegate : MauiUIApplicationDelegate {
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+}
diff --git a/samples/Exceptionless.SampleMaui/Platforms/iOS/Info.plist b/samples/Exceptionless.SampleMaui/Platforms/iOS/Info.plist
new file mode 100644
index 00000000..f2e00c68
--- /dev/null
+++ b/samples/Exceptionless.SampleMaui/Platforms/iOS/Info.plist
@@ -0,0 +1,32 @@
+
+
+
+
+ LSRequiresIPhoneOS
+
+ UIDeviceFamily
+
+ 1
+ 2
+
+ UIRequiredDeviceCapabilities
+
+ arm64
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ XSAppIconAssets
+ Assets.xcassets/appicon.appiconset
+
+
diff --git a/samples/Exceptionless.SampleMaui/Platforms/iOS/Program.cs b/samples/Exceptionless.SampleMaui/Platforms/iOS/Program.cs
new file mode 100644
index 00000000..7d22b9f6
--- /dev/null
+++ b/samples/Exceptionless.SampleMaui/Platforms/iOS/Program.cs
@@ -0,0 +1,9 @@
+using UIKit;
+
+namespace Exceptionless.SampleMaui;
+
+public static class Program {
+ private static void Main(string[] args) {
+ UIApplication.Main(args, null, typeof(AppDelegate));
+ }
+}
diff --git a/samples/Exceptionless.SampleMaui/README.md b/samples/Exceptionless.SampleMaui/README.md
new file mode 100644
index 00000000..dbb321c4
--- /dev/null
+++ b/samples/Exceptionless.SampleMaui/README.md
@@ -0,0 +1,32 @@
+# Exceptionless MAUI Sample
+
+This sample uses the core `Exceptionless` client from a .NET MAUI app. There is no MAUI-specific Exceptionless package, so the app registers an `ExceptionlessClient` in MAUI dependency injection and submits handled exceptions, log events, and feature-usage events from the main page.
+
+## Configuration
+
+The sample defaults to the same local development server used by the other samples:
+
+- API key: `LhhP1C9gijpSKCslHHCvwdSIz298twx271nTest`
+- Server URL: `https://ex.dev.localhost:7111`
+
+Override either value with environment variables before launch:
+
+```bash
+export EXCEPTIONLESS_API_KEY="YOUR_API_KEY"
+export EXCEPTIONLESS_SERVER_URL="https://collector.exceptionless.io"
+```
+
+Events are queued under `FileSystem.Current.AppDataDirectory`, `IncludePrivateInformation` is disabled, and the sample has an explicit **Flush Queue** action. The app also asks the client to process the queue when the MAUI application goes to sleep.
+
+## Run
+
+Install the MAUI workload for the .NET SDK used by this repository, then run a target supported by your machine:
+
+```bash
+dotnet workload install maui
+dotnet build samples/Exceptionless.SampleMaui/Exceptionless.SampleMaui.csproj -f net10.0-maccatalyst
+dotnet build samples/Exceptionless.SampleMaui/Exceptionless.SampleMaui.csproj -f net10.0-ios
+dotnet build samples/Exceptionless.SampleMaui/Exceptionless.SampleMaui.csproj -f net10.0-android
+```
+
+Android emulators cannot usually reach a host machine's `localhost` address directly. If you are sending to a local Exceptionless server from Android, use a URL that the emulator can reach and make sure the development certificate is trusted.
diff --git a/samples/Exceptionless.SampleMaui/Resources/AppIcon/appicon.svg b/samples/Exceptionless.SampleMaui/Resources/AppIcon/appicon.svg
new file mode 100644
index 00000000..4426df4e
--- /dev/null
+++ b/samples/Exceptionless.SampleMaui/Resources/AppIcon/appicon.svg
@@ -0,0 +1,6 @@
+
diff --git a/samples/Exceptionless.SampleMaui/Resources/AppIcon/appiconfg.svg b/samples/Exceptionless.SampleMaui/Resources/AppIcon/appiconfg.svg
new file mode 100644
index 00000000..5f558083
--- /dev/null
+++ b/samples/Exceptionless.SampleMaui/Resources/AppIcon/appiconfg.svg
@@ -0,0 +1,5 @@
+
diff --git a/samples/Exceptionless.SampleMaui/Resources/Splash/splash.svg b/samples/Exceptionless.SampleMaui/Resources/Splash/splash.svg
new file mode 100644
index 00000000..03ca0527
--- /dev/null
+++ b/samples/Exceptionless.SampleMaui/Resources/Splash/splash.svg
@@ -0,0 +1,5 @@
+