APN is a Node.js interface to the Apple Push Notification service, providing reliable delivery of push notifications to iOS devices using Apple's HTTP/2-based provider API. It features automatic connection management with connection pooling, retry mechanisms for failed notifications, and support for both token-based and certificate-based authentication.
npm install apnconst apn = require("apn");ES6/TypeScript:
import * as apn from "apn";
// or destructured
import { Provider, Notification, token } from "apn";const apn = require("apn");
// Create provider with token-based authentication
const provider = new apn.Provider({
token: {
key: "path/to/APNsAuthKey_XXXXXXXXXX.p8",
keyId: "key-id",
teamId: "developer-team-id"
},
production: false // Use sandbox environment
});
// Create notification
const notification = new apn.Notification();
notification.expiry = Math.floor(Date.now() / 1000) + 3600; // Expires in 1 hour
notification.badge = 3;
notification.sound = "ping.aiff";
notification.alert = "📧 ✉ You have a new message";
notification.payload = { messageFrom: "John Appleseed" };
notification.topic = "com.example.MyApp"; // Your app bundle ID
// Send notification
const deviceToken = "a9d0ed10e9cfd022a61cb08753f49c5a0b0dfb383697bf9f9d750a1003da19c7";
provider.send(notification, deviceToken).then((result) => {
console.log("Sent:", result.sent.length);
console.log("Failed:", result.failed.length);
// Log any failures
result.failed.forEach((failure) => {
console.error("Failed to send to", failure.device, ":", failure.error || failure.response?.reason);
});
});
// Shutdown when done
provider.shutdown();The APN package is built around three core components:
The Provider class manages connections to Apple Push Notification service and handles notification delivery.
class Provider extends EventEmitter {
constructor(options: ProviderOptions);
send(notification: Notification, recipients: string | string[]): Promise<Responses>;
shutdown(): void;
}
interface ProviderOptions {
token?: ProviderToken;
cert?: string | Buffer;
key?: string | Buffer;
ca?: (string | Buffer)[];
pfx?: string | Buffer;
passphrase?: string;
production?: boolean;
rejectUnauthorized?: boolean;
connectionRetryLimit?: number;
}
interface ProviderToken {
key: Buffer | string;
keyId: string;
teamId: string;
}
interface Responses {
sent: ResponseSent[];
failed: ResponseFailure[];
}
interface ResponseSent {
device: string;
}
interface ResponseFailure {
device: string;
error?: Error;
status?: string;
response?: {
reason: string;
timestamp?: string;
};
}Usage Examples:
// Token-based authentication (recommended)
const provider = new apn.Provider({
token: {
key: "path/to/APNsAuthKey_XXXXXXXXXX.p8", // or Buffer with key data
keyId: "ABCDEFGHIJ",
teamId: "TEAMID1234"
},
production: true
});
// Certificate-based authentication
const provider = new apn.Provider({
cert: "path/to/cert.pem", // or Buffer with certificate data
key: "path/to/key.pem", // or Buffer with key data
passphrase: "keypassword", // if key is encrypted
production: true
});
// PKCS12 format
const provider = new apn.Provider({
pfx: "path/to/cert.p12", // or Buffer with PKCS12 data
passphrase: "p12password",
production: true
});
// Send to multiple devices
const deviceTokens = ["token1", "token2", "token3"];
provider.send(notification, deviceTokens).then((result) => {
console.log(`Successfully sent to ${result.sent.length} devices`);
console.log(`Failed to send to ${result.failed.length} devices`);
});The Notification class represents a push notification with all its properties and serialization methods.
class Notification {
constructor(payload?: any);
// Core properties
topic: string;
id: string;
expiry: number;
priority: number;
collapseId: string;
threadId: string;
payload: any;
rawPayload: any;
// APS properties
aps: Aps;
badge: number;
sound: string;
alert: string | NotificationAlertOptions;
contentAvailable: boolean;
mutableContent: boolean;
mdm: string | Object;
urlArgs: string[];
category: string;
// Methods
headers(): Object;
compile(): string;
length(): number;
toJSON(): Object;
// Setter methods (return this for chaining)
setPayload(value: any): this;
setExpiry(value: number): this;
setPriority(value: number): this;
setAlert(value: string | NotificationAlertOptions): this;
setBody(value: string): this;
setTitle(value: string): this;
setSubtitle(value: string): this;
setBadge(value: number): this;
setSound(value: string): this;
setContentAvailable(value: boolean): this;
setMutableContent(value: boolean): this;
setMdm(value: string | Object): this;
setUrlArgs(value: string[]): this;
setCategory(value: string): this;
setThreadId(value: string): this;
setLocKey(value: string): this;
setLocArgs(value: string[]): this;
setTitleLocKey(value: string): this;
setTitleLocArgs(value: string[]): this;
setAction(value: string): this;
setActionLocKey(value: string): this;
setLaunchImage(value: string): this;
}
interface NotificationAlertOptions {
title?: string;
subtitle?: string;
body: string;
"title-loc-key"?: string;
"title-loc-args"?: string[];
"action-loc-key"?: string;
"loc-key"?: string;
"loc-args"?: string[];
"launch-image"?: string;
}
interface Aps {
alert?: string | ApsAlert;
"launch-image"?: string;
badge?: number;
sound?: string;
"content-available"?: undefined | 1;
"mutable-content"?: undefined | 1;
"url-args"?: string[];
category?: string;
"thread-id"?: string;
}
interface ApsAlert {
body?: string;
"loc-key"?: string;
"loc-args"?: any[];
title?: string;
"title-loc-key"?: string;
"title-loc-args"?: any[];
action?: string;
"action-loc-key"?: string;
}Usage Examples:
// Basic notification
const notification = new apn.Notification();
notification.topic = "com.example.MyApp";
notification.alert = "Hello World!";
notification.badge = 1;
notification.sound = "default";
// Complex alert with localization
notification.alert = {
title: "New Message",
subtitle: "From John",
body: "Hey there! How are you?",
"loc-key": "MESSAGE_BODY",
"loc-args": ["John", "Hey there! How are you?"]
};
// Method chaining
const notification = new apn.Notification()
.setTopic("com.example.MyApp")
.setAlert("Hello World!")
.setBadge(1)
.setSound("default")
.setPayload({ customData: "value" });
// Silent notification for background refresh
const silentNotification = new apn.Notification();
silentNotification.topic = "com.example.MyApp";
silentNotification.contentAvailable = true;
silentNotification.payload = { updateType: "background-sync" };
// Notification with expiry and priority
notification.expiry = Math.floor(Date.now() / 1000) + (24 * 60 * 60); // 24 hours
notification.priority = 5; // Power-conserving delivery
// Custom payload with raw JSON
notification.rawPayload = {
aps: {
alert: "Custom notification",
badge: 1
},
customField: "customValue",
deepLink: "myapp://profile/123"
};Utility function for validating and normalizing device tokens received from iOS devices.
/**
* Validates and normalizes a device token
* @param input - Device token as string or Buffer
* @returns Normalized hex string token
* @throws Error if token has invalid length after cleanup
*/
function token(input: string | Buffer): string;Usage Examples:
const apn = require("apn");
// Validate string token
const validToken = apn.token("a9d0ed10e9cfd022a61cb08753f49c5a0b0dfb383697bf9f9d750a1003da19c7");
console.log(validToken); // Clean hex string
// Validate Buffer token
const bufferToken = Buffer.from("a9d0ed10e9cfd022a61cb08753f49c5a0b0dfb383697bf9f9d750a1003da19c7", "hex");
const validatedToken = apn.token(bufferToken);
// Handle validation errors
try {
const cleanToken = apn.token("invalid!token@with#special$chars");
} catch (error) {
console.error("Invalid token:", error.message);
}
// Clean up token with spaces and other characters
const messyToken = "a9d0 ed10 e9cf-d022 a61c b087";
const cleanToken = apn.token(messyToken); // Returns clean hex stringThe APN package provides comprehensive error handling for common scenarios:
// Handle provider send failures
provider.send(notification, deviceTokens).then((result) => {
result.failed.forEach((failure) => {
console.error(`Failed to send to ${failure.device}:`);
if (failure.error) {
// Network or connection error
console.error("Error:", failure.error.message);
}
if (failure.response) {
// APNS returned an error
console.error("APNS Error:", failure.response.reason);
// Handle specific error reasons
switch (failure.response.reason) {
case "BadDeviceToken":
// Remove invalid token from database
break;
case "Unregistered":
// Device uninstalled app, remove token
break;
case "PayloadTooLarge":
// Notification payload exceeds 4KB limit
break;
case "TooManyProviderTokenUpdates":
// Rate limited, retry later
break;
}
}
});
});
// Handle token validation errors
try {
const validToken = apn.token(userInputToken);
} catch (error) {
console.error("Invalid device token format");
}// Development configuration
const devProvider = new apn.Provider({
token: {
key: process.env.APN_KEY_PATH,
keyId: process.env.APN_KEY_ID,
teamId: process.env.APN_TEAM_ID
},
production: false
});
// Production configuration
const prodProvider = new apn.Provider({
token: {
key: fs.readFileSync(process.env.APN_KEY_PATH),
keyId: process.env.APN_KEY_ID,
teamId: process.env.APN_TEAM_ID
},
production: true
});
// Enterprise configuration with custom CA
const enterpriseProvider = new apn.Provider({
cert: process.env.APN_CERT_PATH,
key: process.env.APN_KEY_PATH,
ca: [fs.readFileSync("corporate-ca.pem")],
production: true,
connectionRetryLimit: 5
});