KyotoTech PN - Push Notification Library

Developer Documentation v1.0.0 January 2026

Introduction

KyotoTech PN is a cross-platform push notification library for .NET 8.0 that provides a unified API for sending notifications to iOS (APNS) and Android (FCM) devices.

MIT License

KyotoTech PN is open source and free to use under the MIT License. No license key required.

Namespace

All KyotoTech PN types are organized in the following namespaces:

using KyotoTechPN;              // Global configuration
using KyotoTechPN.Models;       // NotificationMessage, configurations
using KyotoTechPN.Services.Apns; // APNS service
using KyotoTechPN.Services.Fcm;  // FCM service

Features

APNS (Apple Push Notification Service)

  • Certificate-based authentication - Using .p12 files
  • JWT token-based authentication - Using .p8 files (recommended)
  • Environment support - Sandbox and Production
  • Silent notifications - Background data updates
  • Batch sending - Send to multiple devices efficiently

FCM (Firebase Cloud Messaging)

  • Service account authentication - Using JSON credentials
  • Multicast support - Batch sending to multiple tokens
  • Data-only notifications - Silent/background messages
  • Singleton pattern - Efficient Firebase app management

Cross-Platform

  • Windows, Linux, macOS - Full platform support
  • Docker compatible - Ready for containerized deployments
  • Automatic path normalization - Cross-platform file paths
  • Async/await - Modern asynchronous API

System Requirements

Supported Platforms

Platform Version Status
Windows10/11, Server 2016+Fully Supported
macOS10.15+ (Catalina)Fully Supported
LinuxUbuntu 18.04+, Debian 10+, RHEL 8+Fully Supported
DockerAny .NET 8 compatible imageFully Supported

Framework Requirements

  • .NET 8.0 or later
  • FirebaseAdmin 3.1.0 (included as dependency)
  • System.IdentityModel.Tokens.Jwt 8.3.1 (included as dependency)

External Requirements

  • Apple Developer Account - For APNS credentials
  • Firebase Project - For FCM credentials

Installation

Via .NET CLI

dotnet add package KyotoTechPN

Via Package Manager Console

Install-Package KyotoTechPN

Via PackageReference

<PackageReference Include="KyotoTechPN" Version="1.0.0" />

Quick Start

APNS (iOS) - JWT Authentication

JWT authentication using .p8 files is the recommended approach for APNS:

using KyotoTechPN.Models;
using KyotoTechPN.Services.Apns;

// Configure APNS with JWT authentication
var config = new ApnsConfiguration
{
    BundleId = "com.yourcompany.app",
    PrivateKeyPath = "/path/to/AuthKey.p8",
    KeyId = "XXXXXXXXXX",      // From Apple Developer Portal
    TeamId = "YYYYYYYYYY",     // From Apple Developer Portal
    UseSandbox = false         // true for development
};

// Create service and send
using var apns = new ApnsService(config);

var message = new NotificationMessage
{
    Title = "Hello",
    Body = "World",
    Sound = "default"
};

var result = await apns.SendAsync(deviceToken, message);

if (result.IsSuccess)
    Console.WriteLine("Notification sent!");
else
    Console.WriteLine($"Error: {result.ErrorMessage}");

FCM (Android)

using KyotoTechPN.Models;
using KyotoTechPN.Services.Fcm;

// Configure FCM
var config = new FcmConfiguration
{
    CredentialsPath = "/path/to/firebase-credentials.json"
};

// Create service and send
using var fcm = new FcmService(config);

var message = new NotificationMessage
{
    Title = "Hello",
    Body = "World"
};

var result = await fcm.SendAsync(deviceToken, message);

API Reference

NotificationMessage

The message payload sent to devices:

Property Type Description
TitlestringNotification title
BodystringNotification body text
Subtitlestring?Subtitle (APNS only)
Soundstring?Sound name or "default"
Badgeint?Badge count (APNS only)
CustomDataDictionary<string, string>?Custom key-value data
PriorityNotificationPriorityHigh or Normal
TimeToLiveSecondsint?Expiration time in seconds

NotificationResult

Result of a single notification send operation:

Property Type Description
IsSuccessboolWhether the notification was sent successfully
ErrorCodestring?Error code if failed
ErrorMessagestring?Human-readable error message

BatchNotificationResult

Result of a batch notification send operation:

Property Type Description
SuccessCountintNumber of successful sends
FailureCountintNumber of failed sends
ResultsList<NotificationResult>Individual results per token

ApnsConfiguration

Property Type Description
BundleIdstringApp bundle identifier (required)
UseSandboxboolUse development environment
CertificatePathstring?Path to .p12 certificate file
CertificatePasswordstring?Certificate password
PrivateKeyPathstring?Path to .p8 private key file
KeyIdstring?Key ID from Apple Developer Portal
TeamIdstring?Team ID from Apple Developer Portal

FcmConfiguration

Property Type Description
CredentialsPathstringPath to Firebase JSON credentials (required)
ProjectIdstring?Firebase project ID (auto-detected from credentials)

ApnsService Methods

Method Description
SendAsync(string token, NotificationMessage message)Send to single device
SendAsync(IEnumerable<string> tokens, NotificationMessage message)Send to multiple devices
SendSilentAsync(string token, Dictionary<string, string> data)Send silent notification
SendSilentAsync(IEnumerable<string> tokens, Dictionary<string, string> data)Send silent to multiple

FcmService Methods

Method Description
SendAsync(string token, NotificationMessage message)Send to single device
SendAsync(IEnumerable<string> tokens, NotificationMessage message)Send to multiple devices
SendSilentAsync(string token, Dictionary<string, string> data)Send data-only message
SendSilentAsync(IEnumerable<string> tokens, Dictionary<string, string> data)Send data-only to multiple

Code Examples

Batch Sending

var tokens = new List<string>
{
    "device_token_1",
    "device_token_2",
    "device_token_3"
};

var message = new NotificationMessage
{
    Title = "Broadcast",
    Body = "Message to all users"
};

var batchResult = await apns.SendAsync(tokens, message);

Console.WriteLine($"Success: {batchResult.SuccessCount}");
Console.WriteLine($"Failed: {batchResult.FailureCount}");

// Check individual results
foreach (var result in batchResult.Results)
{
    if (!result.IsSuccess)
        Console.WriteLine($"Failed: {result.ErrorMessage}");
}

Silent Notifications

Silent notifications wake your app in the background without displaying an alert:

var customData = new Dictionary<string, string>
{
    { "action", "sync" },
    { "id", "12345" },
    { "timestamp", DateTime.UtcNow.ToString("O") }
};

// APNS silent notification
var result = await apns.SendSilentAsync(deviceToken, customData);

// FCM data-only message
var fcmResult = await fcm.SendSilentAsync(deviceToken, customData);

Rich Notifications with Custom Data

var message = new NotificationMessage
{
    Title = "New Order",
    Body = "You have a new order #12345",
    Subtitle = "Restaurant App",           // APNS only
    Sound = "notification.wav",
    Badge = 5,                             // APNS only
    Priority = NotificationPriority.High,
    TimeToLiveSeconds = 3600,              // 1 hour
    CustomData = new Dictionary<string, string>
    {
        { "orderId", "12345" },
        { "screen", "order_details" }
    }
};

var result = await apns.SendAsync(deviceToken, message);

APNS with Certificate Authentication

var config = new ApnsConfiguration
{
    BundleId = "com.yourcompany.app",
    CertificatePath = "/path/to/certificate.p12",
    CertificatePassword = "your_password",
    UseSandbox = true  // Development environment
};

using var apns = new ApnsService(config);
// ... send notifications

Complete Server Example

using KyotoTechPN;
using KyotoTechPN.Models;
using KyotoTechPN.Services.Apns;
using KyotoTechPN.Services.Fcm;

public class NotificationService : IDisposable
{
    private readonly ApnsService _apns;
    private readonly FcmService _fcm;

    public NotificationService()
    {
        // Enable logging
        PushNotificationConfig.ConfigureLogging(msg =>
            Console.WriteLine($"[PN] {msg}"));

        // Initialize APNS
        _apns = new ApnsService(new ApnsConfiguration
        {
            BundleId = Environment.GetEnvironmentVariable("APNS_BUNDLE_ID")!,
            PrivateKeyPath = Environment.GetEnvironmentVariable("APNS_KEY_PATH")!,
            KeyId = Environment.GetEnvironmentVariable("APNS_KEY_ID")!,
            TeamId = Environment.GetEnvironmentVariable("APNS_TEAM_ID")!,
            UseSandbox = false
        });

        // Initialize FCM
        _fcm = new FcmService(new FcmConfiguration
        {
            CredentialsPath = Environment.GetEnvironmentVariable("FCM_CREDENTIALS")!
        });
    }

    public async Task<bool> SendToUserAsync(
        string userId,
        string title,
        string body)
    {
        // Get user's device tokens from database
        var devices = await GetUserDevicesAsync(userId);

        var message = new NotificationMessage
        {
            Title = title,
            Body = body,
            Sound = "default"
        };

        var tasks = new List<Task<NotificationResult>>();

        foreach (var device in devices)
        {
            if (device.Platform == "ios")
                tasks.Add(_apns.SendAsync(device.Token, message));
            else
                tasks.Add(_fcm.SendAsync(device.Token, message));
        }

        var results = await Task.WhenAll(tasks);
        return results.All(r => r.IsSuccess);
    }

    public void Dispose()
    {
        _apns?.Dispose();
        _fcm?.Dispose();
    }
}

Configuration

Logging

using KyotoTechPN;

// Enable logging with default Console.WriteLine
PushNotificationConfig.ConfigureLogging();

// Custom logger
PushNotificationConfig.ConfigureLogging(msg =>
    MyLogger.Log(LogLevel.Info, msg));

// Disable logging
PushNotificationConfig.DisableLogging();

Getting APNS Credentials

  1. Go to Apple Developer Portal
  2. Navigate to Certificates, Identifiers & ProfilesKeys
  3. Create a new key with Apple Push Notifications service (APNs) enabled
  4. Download the .p8 file (only available once!)
  5. Note your Key ID and Team ID

Getting FCM Credentials

  1. Go to Firebase Console
  2. Select your project → Project SettingsService accounts
  3. Click Generate new private key
  4. Save the JSON file securely

Security Warning

Never commit credential files (.p8, .p12, .json) to version control. Use environment variables or secure secret management in production.

Troubleshooting

Common APNS Errors

"BadDeviceToken"

Cause: The device token is invalid or was generated for a different environment.

Solution:

  • Ensure UseSandbox matches your app build (Debug = sandbox, Release = production)
  • Verify the token is current and not expired
  • Remove invalid tokens from your database

"Unregistered"

Cause: The app was uninstalled from the device.

Solution: Remove this token from your database.

"TopicDisallowed"

Cause: Bundle ID doesn't match the certificate.

Solution: Verify BundleId matches your app's bundle identifier exactly.

Common FCM Errors

"InvalidRegistration"

Cause: The FCM token is malformed or expired.

Solution: Request a new token from the client app.

"NotRegistered"

Cause: The app was uninstalled or the token was invalidated.

Solution: Remove this token from your database.

"SenderIdMismatch"

Cause: The credentials don't match the project that generated the token.

Solution: Verify you're using the correct Firebase project credentials.

Path Issues on Different Platforms

KyotoTech PN automatically normalizes file paths for cross-platform compatibility. However, ensure paths are accessible:

// Use absolute paths
var config = new ApnsConfiguration
{
    PrivateKeyPath = Path.Combine(AppContext.BaseDirectory, "credentials", "AuthKey.p8")
};

Support & Contact

Resources

External Documentation

Legal Disclaimer

KyotoTech PN is provided "AS IS" without warranty. You are solely responsible for compliance with Apple's APNS Terms, Google's FCM Terms, and applicable privacy laws (GDPR, CCPA, etc.).

Copyright 2026 KyotoTech LLC. All Rights Reserved.
This documentation is for KyotoTech PN version 1.0.0.