airhorn

tests codecov license npm npm

Cloud Native Notifications

Airhorn simplifies the process of notifications by using templates to send messages across various providers with a common easy to use interface.

  • Supports multiple notification types: SMS, Email, Mobile Push, Webhooks
  • A unified API for all notification types using the send() method.
  • Hooks and Emitting built in by default for extendability and observability.
  • Send Strategy (Round Robin, Fail Over, All) Choose the best delivery method for each notification.
  • Built in Webhook support for sending notifications to external services.
  • Built in support for retries and error handling on sends.
  • Advanced caching on template compilation and execution.
  • Load a template from a file for easy GitOps based workflows.
  • Many supported providers such as Twilio (with Sendgrid), AWS, and Google Cloud.
  • Robust (6+ template formats) templating via ecto
  • Easily build your own provider with minimal effort via AirhornProvider interface.
  • Statistics tracking for send successes, failures, and execution times (instance only).
  • ESM and Typescript based supporting Nodejs 20+
  • Maintained on a regular basis with updates and improvements.

Table of Contents

Getting Started

To get started with Airhorn, you can install it via npm:

npm install airhorn

Once installed, this gives you the main send functionality and built in webhook support. You can use it in your project like so:

import { Airhorn, AirhornProviderType } from "airhorn";

const airhorn = new Airhorn();

const template = {
	from: "https://mywebhookdomain.com",
	content: "Hey <%= name %> this is a test message from Airhorn",
	templateEngine: "ejs",
}

const data = { name: "John" };

await airhorn.send("https://mockhttp.org/post", template, data, AirhornProviderType.WEBHOOK);

Now lets configure the Airhorn instance with your preferred providers such as Twilio for SMS and SendGrid for Email.

npm install airhorn @airhorn/twilio
import { Airhorn, AirhornProviderType } from "airhorn";
import { AirhornTwilio } from "@airhorn/twilio";

const providers = [
	new AirhornTwilio({
		accountSid: "your_account_sid",
		authToken: "your_auth_token"
	}),
];

const airhorn = new Airhorn({
	providers
});

// this will give you twilio and webhook (built in) support. Now lets create a template and send it!
const template = {
	from: "+12223334444",
	content: "Hey <%= name %> this is a test message from Airhorn",
	templateEngine: "ejs",
}

const data = { name: "John" };

await airhorn.send("+1234567890", template, data, AirhornProviderType.SMS);

To learn about the available providers and their capabilities, check the Providers section.

Airhorn Options

Airhorn provides a variety of options to customize its behavior. You can configure these options when creating an instance of the Airhorn class:

import { Airhorn, AirhornSendStrategy } from "airhorn";

const airhorn = new Airhorn({
	sendStrategy: AirhornSendStrategy.RoundRobin
});

Here is the AirhornOptions type:

export type AirhornOptions = {
	/**
	 * Whether to enable caching.
	 * @default true
	 */
	cache?: boolean | Cacheable | CacheableOptions;
	/**
	 * Whether to collect statistics.
	 * @default false
	 */
	statistics?: boolean;
	/**
	 * The providers to add to the Airhorn instance. AirhornWebhook is added by default unless `useWebhookProvider` is set to false.
	 */
	providers?: Array<AirhornProvider>;
	/**
	 * Whether to use the built-in webhook provider.
	 * @default true
	 */
	useWebhookProvider?: boolean;
	/**
	 * The retry strategy to use when sending messages.
	 * @default 0
	 */
	retryStrategy?: AirhornRetryStrategy;
	/**
	 * The timeout to use when sending messages.
	 * @default 100
	 */
	timeout?: number;
	/**
	 * The send strategy to use when sending messages.
	 * @default AirhornSendStrategy.RoundRobin
	 */
	sendStrategy?: AirhornSendStrategy;
	/**
	 * Whether to throw an error if sending fails. By default we use emitting for errors
	 * @default false
	 */
	throwOnErrors?: boolean;
};

Using Send Helper Methods

Airhorn provides helper methods for common tasks. For example, you can use the sendSMS method to send SMS messages easily:

import { Airhorn } from "airhorn";
import { AirhornTwilio } from "@airhorn/twilio";

const providers = [
	new AirhornTwilio({
		accountSid: "your_account_sid",
		authToken: "your_auth_token"
	}),
];

const airhorn = new Airhorn({
	providers
});

const template = {
	from: "+12223334444",
	content: "Hey <%= name %> this is a test message from Airhorn",
	templateEngine: "ejs",
}

const data = { name: "John" };

await airhorn.sendSMS("+1234567890", template, data);

Here are the following helper methods available:

  • sendSMS: Sends an SMS message.
  • sendEmail: Sends an email message.
  • sendMobilePush: Sends a mobile push notification.
  • sendWebhook: Sends a webhook notification.

Airhorn Send Response

The send method returns an AirhornSendResult object that contains information about the send operation. Here is the structure of the AirhornSendResult type:

export type AirhornSendResult = {
	/**
	 * The providers that were used to send the message.
	 */
	providers: Array<AirhornProvider>;
	/**
	 * The message that was sent.
	 */
	message?: AirhornProviderMessage;
	/**
	 * Whether the message was sent successfully.
	 */
	success: boolean;
	/**
	 * The response from the provider.
	 */
	// biome-ignore lint/suspicious/noExplicitAny: expected
	response: any;
	/**
	 * The number of times the message was retried.
	 */
	retries: number;
	/**
	 * The errors that occurred while sending the message.
	 */
	errors: Array<Error>;
	/**
	 * The time taken to execute the send operation.
	 */
	executionTime: number;
};

Send Strategies

Airhorn supports multiple send strategies to control how notifications are delivered. You can choose from the following strategies:

  • Round Robin: Distributes notifications evenly across all available providers.
  • Fail Over: Tries each provider in order until one succeeds.
  • All: Sends the notification to all providers simultaneously.

You can configure the send strategy when creating the Airhorn instance:

import { Airhorn, AirhornSendStrategy } from "airhorn";

const airhorn = new Airhorn({
	sendStrategy: AirhornSendStrategy.RoundRobin
});

Airhorn API

Here are all the properties and methods available and a brief description of each:

  • .cache: Gets the cache instance which is based on cacheable.
  • .retryStrategy: Gets the retry strategy.
  • .timeout: Gets the timeout for sending messages.
  • .sendStrategy: Gets the send strategy.
  • .throwOnErrors: Gets the throw on errors flag.
  • .statistics: Access the statistics instance. go to Statistics to learn more.
  • .providers: Gets the list of configured providers.
  • send(): Sends a message using the configured providers.
  • sendSMS(): Sends an SMS message.
  • sendEmail(): Sends an email message.
  • sendMobilePush(): Sends a mobile push notification.
  • sendWebhook(): Sends a webhook notification.
  • loadTemplate(): Helper method that loads a template from the file system. Go to Load Template Helper to learn more.
  • getProvidersByType(): Gets the list of providers by type. (Used Internally)
  • setCache(): Sets the cache instance. (Used Internally)
  • addProviders(): Adds new providers to the Airhorn instance. (Used Internally)
  • generateMessage(): Generates a message from a template and data. (Used Internally)

Using Webhooks

Webhooks is built into Airhorn as a default provider and can be used to send notifications to external services. To use the built in webhooks just create an instance of the Airhorn class and call the send or sendWebhook method.

An example using the send method (recommended):

import { Airhorn, AirhornProviderType } from "airhorn";

const template = {
	from: "+12223334444",
	to: "+1234567890",
	content: "Hey <%= name %> this is a test message from Airhorn",
	templateEngine: "ejs",
}

const data = { name: "John" };

await airhorn.send("https://mockhttp.org/post", template, data, AirhornProviderType.WEBHOOK);

To send using the helper function sendWebhook:

import { Airhorn } from "airhorn";

const airhorn = new Airhorn();

const template = {
	from: "+12223334444",
	to: "+1234567890",
	content: "Hey <%= name %> this is a test message from Airhorn",
	templateEngine: "ejs",
}

const data = { name: "John" };

await airhorn.sendWebhook("https://mockhttp.org/post", template, data);

Statistics

Airhorn provides built-in statistics to help you monitor the performance of your notifications. You can access the statistics instance through the .statistics property:

import { Airhorn } from "airhorn";

const airhorn = new Airhorn({ statistics: true });

// Now you can use the stats object to get information about sent notifications
console.log(`Total Sends: ${airhorn.statistics.totalSends}`);
console.log(`Total Successful Sends: ${airhorn.statistics.totalSendSuccesses}`);
console.log(`Total Failed Sends: ${airhorn.statistics.totalSendFailures}`);

// execution time statistics
console.log(`Total Execution Time: ${airhorn.statistics.totalExecutionTime} ms`);
console.log(`Average Execution Time: ${airhorn.statistics.averageExecutionTime} ms`);
console.log(`Minimum Execution Time: ${airhorn.statistics.minimumExecutionTime} ms`);
console.log(`Maximum Execution Time: ${airhorn.statistics.maxExecutionTime} ms`);

// execution time data Array
console.log(`All Execution Times: ${airhorn.statistics.executionTimes}`);
console.log(`Slowest Execution Times: ${airhorn.statistics.slowestExecutionTimes}`);
console.log(`Fastest Execution Times: ${airhorn.statistics.fastestExecutionTimes}`);

By default, Airhorn statistics are disabled. You can enable them by setting the statistics option to true when creating the Airhorn instance. If you want to enable it after the Airhorn instance is created do the following:

import { Airhorn } from "airhorn";

const airhorn = new Airhorn();

airhorn.statistics.enable();

To reset the statistics, you can call the reset method:

import { Airhorn } from "airhorn";

const airhorn = new Airhorn();

airhorn.statistics.reset();

Emitting Events

Airhorn provides event emitting by default with the following events:

  • error: Emitted when there is an error.
  • send.success: Emitted when a notification is successfully sent.
  • send.failure: Emitted when a notification fails to send.

You can listen for these events using the on method:

import { Airhorn, AirhornEvent, type AirhornSendResult } from "airhorn";

const airhorn = new Airhorn();

airhorn.on(AirhornEvent.SendSuccess, (data: AirhornSendResult) => {
  console.log(`Notification sent successfully: ${data}`);
});

airhorn.on(AirhornEvent.SendFailure, (data: AirhornSendResult) => {
  console.error(`Failed to send notification: ${data}`);
});

Load Template Helper

In previous versions of Airhon we used the file system to load all the templates into a store that was used by the instance. Now, we offer an easy method to just load it from a markdown file if you want from anywhere on the file system.

Here is an example of how to use the loadTemplate helper method:

import { Airhorn } from "airhorn";

const airhorn = new Airhorn();

const template = await airhorn.loadTemplate("path/to/template.md");

// now you can send with that template
await airhorn.send("https://mockhttp.org/post", template, data, AirhornProviderType.WEBHOOK);

An example of the markdown format is located at ./packages/airhorn/test/fixtures.

Core Supported Providers

We currently support twilio, aws, and azure with thier offerings. Here is a chart showing what functionality is in each:

Provider SMS Email Push Webhook
(built in airhorn)
@airhorn/twilio
@airhorn/aws
@airhorn/azure

Note: We used to support firebase because of mobile push but it made more sense to focus on aws and azure because it is more comprehensive.

Third Party Providers

If you have built a provider library let us know! We are more than happy to list it here!

Creating a Provider

To create a provider you can extend the AirhornProvider interface and implement the required methods for your specific provider.

import { AirhornProvider } from "airhorn";

class MyCustomProvider implements AirhornProvider {
  // Implement required methods
}

Once implemented, you can use your custom provider just like any other provider in Airhorn.

import { Airhorn, AirhornProvider } from "airhorn";

class MyCustomProvider implements AirhornProvider {
  // Implement required methods
}
const airhorn = new Airhorn({
  providers: [new MyCustomProvider()]
});

Use one of the built in providers as a reference such as @airhorn/twilio.

How to Contribute

Now that you've set up your workspace, you're ready to contribute changes to the airhorn repository you can refer to the CONTRIBUTING guide. If you have any questions please feel free to ask by creating an issue and label it question.

Licensing and Copyright

This project is MIT License © Jared Wray