Adapter Reference
All available channel adapters, their configuration fields, native bulk support, and channel-specific Metadata keys.
SendGrid
dotnet add package RecurPixel.Notify.Email.SendGrid
"Email": {
"Provider": "sendgrid",
"SendGrid": {
"ApiKey": "SG.xxxxxxxxxx",
"FromEmail": "no-reply@yourapp.com",
"FromName": "Your App"
}
}
| Field | Required | Description |
|---|---|---|
ApiKey |
✅ | SendGrid API key (starts with SG.) |
FromEmail |
✅ | Verified sender email address |
FromName |
❌ | Display name for the sender |
Payload fields used: To (recipient email), Subject, Body (HTML supported).
Native bulk: ✅ Yes — up to 1,000 recipients per API call. SendBulkAsync uses the SendGrid batch endpoint automatically.
Metadata keys:
| Key | Description |
|---|---|
html |
Set to "false" to send plain text instead of HTML |
reply_to |
Reply-to email address |
categories |
Comma-separated SendGrid categories for analytics |
SMTP
dotnet add package RecurPixel.Notify.Email.Smtp
"Email": {
"Provider": "smtp",
"Smtp": {
"Host": "smtp.office365.com",
"Port": 587,
"Username": "no-reply@yourapp.com",
"Password": "your-password",
"UseSsl": true,
"FromEmail": "no-reply@yourapp.com",
"FromName": "Your App"
}
}
| Field | Required | Description |
|---|---|---|
Host |
✅ | SMTP server hostname |
Port |
✅ | SMTP port (typically 587 or 465) |
Username |
✅ | SMTP authentication username |
Password |
✅ | SMTP authentication password |
UseSsl |
✅ | Enable TLS/SSL |
FromEmail |
✅ | Sender email address |
FromName |
❌ | Sender display name |
Native bulk: ❌ No — base class loops single sends with a concurrency cap.
Mailgun
dotnet add package RecurPixel.Notify.Email.Mailgun
"Email": {
"Provider": "mailgun",
"Mailgun": {
"ApiKey": "key-xxxxxxxxxx",
"Domain": "mg.yourapp.com",
"FromEmail": "no-reply@mg.yourapp.com",
"FromName": "Your App",
"Region": "us"
}
}
| Field | Required | Description |
|---|---|---|
ApiKey |
✅ | Mailgun private API key |
Domain |
✅ | Your verified Mailgun sending domain |
FromEmail |
✅ | Sender email |
FromName |
❌ | Sender display name |
Region |
❌ | "us" (default) or "eu" |
Native bulk: ✅ Yes — uses Mailgun’s recipient variables API.
Resend
dotnet add package RecurPixel.Notify.Email.Resend
"Email": {
"Provider": "resend",
"Resend": {
"ApiKey": "re_xxxxxxxxxx",
"FromEmail": "no-reply@yourapp.com",
"FromName": "Your App"
}
}
| Field | Required | Description |
|---|---|---|
ApiKey |
✅ | Resend API key |
FromEmail |
✅ | Verified sender email |
FromName |
❌ | Sender display name |
Native bulk: ❌ No — base class loops single sends.
Postmark
dotnet add package RecurPixel.Notify.Email.Postmark
"Email": {
"Provider": "postmark",
"Postmark": {
"ApiKey": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"FromEmail": "no-reply@yourapp.com",
"FromName": "Your App"
}
}
| Field | Required | Description |
|---|---|---|
ApiKey |
✅ | Postmark server API token |
FromEmail |
✅ | Verified sender email |
FromName |
❌ | Sender display name |
Native bulk: ✅ Yes — uses Postmark’s batch messages endpoint.
AWS SES
dotnet add package RecurPixel.Notify.Email.AwsSes
"Email": {
"Provider": "awsses",
"AwsSes": {
"AccessKey": "AKIAIOSFODNN7EXAMPLE",
"SecretKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"Region": "us-east-1",
"FromEmail": "no-reply@yourapp.com",
"FromName": "Your App"
}
}
| Field | Required | Description |
|---|---|---|
AccessKey |
✅ | AWS access key ID |
SecretKey |
✅ | AWS secret access key |
Region |
✅ | AWS region (e.g. us-east-1) |
FromEmail |
✅ | SES-verified sender email |
FromName |
❌ | Sender display name |
Native bulk: ✅ Yes — uses the SES bulk email API.
Azure Communication Services Email
dotnet add package RecurPixel.Notify.Email.AzureCommEmail
"Email": {
"Provider": "azurecommemail",
"AzureCommEmail": {
"ConnectionString": "endpoint=https://youracs.communication.azure.com/;accesskey=xxxxxxxxxx",
"FromEmail": "DoNotReply@youracs.azurecomm.net",
"FromName": "Your App"
}
}
| Field | Required | Description |
|---|---|---|
ConnectionString |
✅ | Azure Communication Services connection string |
FromEmail |
✅ | Sender email registered in ACS |
FromName |
❌ | Sender display name |
Native bulk: ❌ No — base class loops single sends.
SMS
Twilio SMS
dotnet add package RecurPixel.Notify.Sms.Twilio
"Sms": {
"Provider": "twilio",
"Twilio": {
"AccountSid": "ACxxxxxxxxxx",
"AuthToken": "xxxxxxxxxx",
"FromNumber": "+15550001234"
}
}
| Field | Required | Description |
|---|---|---|
AccountSid |
✅ | Twilio Account SID |
AuthToken |
✅ | Twilio Auth Token |
FromNumber |
✅ | Your Twilio phone number (E.164 format) |
Native bulk: ❌ No — base class loops single sends.
Vonage (Nexmo) SMS
dotnet add package RecurPixel.Notify.Sms.Vonage
"Sms": {
"Provider": "vonage",
"Vonage": {
"ApiKey": "xxxxxxxxxx",
"ApiSecret": "xxxxxxxxxx",
"FromNumber": "YourApp"
}
}
| Field | Required | Description |
|---|---|---|
ApiKey |
✅ | Vonage API key |
ApiSecret |
✅ | Vonage API secret |
FromNumber |
✅ | Sender ID or phone number |
Native bulk: ✅ Yes — uses Vonage bulk SMS API.
Sinch
dotnet add package RecurPixel.Notify.Sms.Sinch
"Sms": {
"Provider": "sinch",
"Sinch": {
"ServicePlanId": "xxxxxxxxxx",
"ApiToken": "xxxxxxxxxx",
"FromNumber": "+15550001234"
}
}
| Field | Required | Description |
|---|---|---|
ServicePlanId |
✅ | Sinch service plan ID |
ApiToken |
✅ | Sinch API token |
FromNumber |
✅ | Sinch virtual phone number |
Native bulk: ✅ Yes — uses Sinch batch SMS API.
Plivo
dotnet add package RecurPixel.Notify.Sms.Plivo
"Sms": {
"Provider": "plivo",
"Plivo": {
"AuthId": "xxxxxxxxxx",
"AuthToken": "xxxxxxxxxx",
"FromNumber": "+15550001234"
}
}
| Field | Required | Description |
|---|---|---|
AuthId |
✅ | Plivo Auth ID |
AuthToken |
✅ | Plivo Auth Token |
FromNumber |
✅ | Plivo phone number |
Native bulk: ❌ No — base class loops single sends.
MessageBird
dotnet add package RecurPixel.Notify.Sms.MessageBird
"Sms": {
"Provider": "messagebird",
"MessageBird": {
"ApiKey": "xxxxxxxxxx",
"FromNumber": "YourApp"
}
}
| Field | Required | Description |
|---|---|---|
ApiKey |
✅ | MessageBird API key |
FromNumber |
✅ | Sender name or phone number |
Native bulk: ❌ No — base class loops single sends.
AWS SNS
dotnet add package RecurPixel.Notify.Sms.AwsSns
"Sms": {
"Provider": "awssns",
"AwsSns": {
"AccessKey": "AKIAIOSFODNN7EXAMPLE",
"SecretKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"Region": "us-east-1",
"SenderId": "YourApp"
}
}
| Field | Required | Description |
|---|---|---|
AccessKey |
✅ | AWS access key ID |
SecretKey |
✅ | AWS secret access key |
Region |
✅ | AWS region |
SenderId |
❌ | SMS sender ID (alphanumeric, supported regions only) |
Native bulk: ✅ Yes — publishes to SNS topic for fan-out.
Azure Communication Services SMS
dotnet add package RecurPixel.Notify.Sms.AzureCommSms
"Sms": {
"Provider": "azurecommsms",
"AzureCommSms": {
"ConnectionString": "endpoint=https://youracs.communication.azure.com/;accesskey=xxxxxxxxxx",
"FromNumber": "+18331234567"
}
}
| Field | Required | Description |
|---|---|---|
ConnectionString |
✅ | Azure Communication Services connection string |
FromNumber |
✅ | Phone number purchased from ACS (E.164 format) |
Native bulk: ✅ Yes — uses ACS SMS batch API, chunks of 100 recipients per call.
MSG91
dotnet add package RecurPixel.Notify.Sms.Msg91
"Sms": {
"Provider": "msg91",
"Msg91": {
"AuthKey": "xxxxxxxxxxxxxxxxxxxxxxxxxx",
"SenderId": "NOTIFY",
"Route": "4"
}
}
| Field | Required | Description |
|---|---|---|
AuthKey |
✅ | MSG91 API authentication key |
SenderId |
✅ | Up to 6-character alphanumeric sender ID registered in your MSG91 account |
Route |
❌ | MSG91 route. "4" = transactional (default), "1" = promotional |
Payload fields used: To = recipient phone number (digits only, no +). Body = message text.
Native bulk: ❌ No — MSG91 SMS has no batch API. Base class loops single sends with a concurrency cap.
Push Notifications
FCM (Firebase Cloud Messaging)
dotnet add package RecurPixel.Notify.Push.Fcm
"Push": {
"Provider": "fcm",
"Fcm": {
"ProjectId": "your-firebase-project",
"ServiceAccountJson": "{ \"type\": \"service_account\", ... }"
}
}
| Field | Required | Description |
|---|---|---|
ProjectId |
✅ | Firebase project ID |
ServiceAccountJson |
✅ | Full service account JSON (as a string) |
Native bulk: ✅ Yes — uses FCM multicast, up to 500 device tokens per call.
Payload fields: To = device registration token. Subject = notification title. Body = notification body.
Metadata keys:
| Key | Description |
|---|---|
image_url |
URL of image to show in the notification |
click_action |
Android intent / iOS category |
data |
JSON string of custom key-value data payload |
APNs (Apple Push Notification Service)
dotnet add package RecurPixel.Notify.Push.Apns
"Push": {
"Provider": "apns",
"Apns": {
"KeyId": "XXXXXXXXXX",
"TeamId": "XXXXXXXXXX",
"BundleId": "com.yourapp.app",
"PrivateKey": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
}
}
| Field | Required | Description |
|---|---|---|
KeyId |
✅ | APNs auth key ID (from Apple Developer portal) |
TeamId |
✅ | Apple Developer Team ID |
BundleId |
✅ | App bundle identifier |
PrivateKey |
✅ | Contents of the .p8 private key file |
Native bulk: ❌ No — APNs is one notification per HTTP/2 request. Base class loops.
Metadata keys:
| Key | Description |
|---|---|
badge |
Badge count to display on app icon |
sound |
Sound name, or "default" |
content_available |
"1" for silent/background push |
mutable_content |
"1" to allow notification service extension |
category |
APNs category identifier |
OneSignal
dotnet add package RecurPixel.Notify.Push.OneSignal
"Push": {
"Provider": "onesignal",
"OneSignal": {
"AppId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"ApiKey": "xxxxxxxxxx"
}
}
| Field | Required | Description |
|---|---|---|
AppId |
✅ | OneSignal App ID |
ApiKey |
✅ | OneSignal REST API key |
Native bulk: ✅ Yes — uses OneSignal’s bulk notifications API.
Expo Push
dotnet add package RecurPixel.Notify.Push.Expo
"Push": {
"Provider": "expo",
"Expo": {
"AccessToken": "xxxxxxxxxx"
}
}
| Field | Required | Description |
|---|---|---|
AccessToken |
❌ | Expo access token (required for production apps) |
Native bulk: ✅ Yes — uses Expo’s push ticket batch API.
Twilio WhatsApp
dotnet add package RecurPixel.Notify.WhatsApp.Twilio
"WhatsApp": {
"Provider": "twilio",
"Twilio": {
"AccountSid": "ACxxxxxxxxxx",
"AuthToken": "xxxxxxxxxx",
"FromNumber": "whatsapp:+14155238886"
}
}
| Field | Required | Description |
|---|---|---|
AccountSid |
✅ | Twilio Account SID |
AuthToken |
✅ | Twilio Auth Token |
FromNumber |
✅ | WhatsApp-enabled number with whatsapp: prefix |
Payload: To must include the whatsapp: prefix (e.g. whatsapp:+919876543210).
Native bulk: ❌ No — Meta messaging policy prohibits bulk unsolicited WhatsApp. Base class loops with rate cap.
Meta Cloud API
dotnet add package RecurPixel.Notify.WhatsApp.MetaCloud
"WhatsApp": {
"Provider": "metacloud",
"MetaCloud": {
"PhoneNumberId": "1234567890",
"AccessToken": "EAAxxxxxxxxxxxxx",
"ApiVersion": "v18.0"
}
}
| Field | Required | Description |
|---|---|---|
PhoneNumberId |
✅ | Meta Business phone number ID |
AccessToken |
✅ | Permanent system user access token |
ApiVersion |
❌ | API version (default: v18.0) |
Native bulk: ❌ No — Meta policy restricts bulk WhatsApp sends. Base class loops.
Metadata keys:
| Key | Description |
|---|---|
template_name |
Name of an approved WhatsApp message template |
template_language |
Language code for the template (e.g. en_US) |
Vonage WhatsApp
dotnet add package RecurPixel.Notify.WhatsApp.Vonage
"WhatsApp": {
"Provider": "vonage",
"Vonage": {
"ApiKey": "xxxxxxxxxx",
"ApiSecret": "xxxxxxxxxx",
"FromNumber": "+14155550100"
}
}
| Field | Required | Description |
|---|---|---|
ApiKey |
✅ | Vonage API key |
ApiSecret |
✅ | Vonage API secret |
FromNumber |
✅ | WhatsApp-enabled number registered with Vonage |
Payload fields: To = recipient phone number. Subject and Body are combined as the message text.
Native bulk: ❌ No — Meta policy restricts bulk WhatsApp sends. Base class loops.
MSG91 WhatsApp
dotnet add package RecurPixel.Notify.WhatsApp.Msg91
"WhatsApp": {
"Provider": "msg91",
"Msg91": {
"AuthKey": "xxxxxxxxxxxxxxxxxxxxxxxxxx",
"IntegratedNumber": "919876543210",
"Namespace": "your-waba-namespace"
}
}
| Field | Required | Description |
|---|---|---|
AuthKey |
✅ | MSG91 API authentication key |
IntegratedNumber |
✅ | WhatsApp Business number without + (e.g. 919876543210 for +91 98765 43210) |
Namespace |
❌ | WhatsApp Business Account namespace for approved template messages |
Payload fields: To = recipient phone number (digits only, no +). Body = message text.
Native bulk: ❌ No — Meta messaging policy restricts bulk WhatsApp sends. Base class loops with rate cap.
Team Collaboration
Slack
dotnet add package RecurPixel.Notify.Slack
"Slack": {
"WebhookUrl": "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXX",
"BotToken": "xoxb-xxxxxxxxxx"
}
| Field | Required | Description |
|---|---|---|
WebhookUrl |
✅* | Incoming webhook URL |
BotToken |
✅* | Bot token for Bot API (alternative to webhook) |
*One of WebhookUrl or BotToken is required.
Payload fields: Body = message text. Subject = optional header block.
Metadata keys:
| Key | Description |
|---|---|
channel |
Override the target channel (Bot API only) |
icon_emoji |
Emoji to use as the bot avatar (e.g. :robot_face:) |
username |
Override the bot display name |
blocks |
JSON string of Slack Block Kit blocks |
Native bulk: ❌ No — one message per webhook call. Base class loops.
Discord
dotnet add package RecurPixel.Notify.Discord
"Discord": {
"WebhookUrl": "https://discord.com/api/webhooks/000000000000000000/xxxxxxxxxxxx"
}
| Field | Required | Description |
|---|---|---|
WebhookUrl |
✅ | Discord webhook URL |
Payload fields: Body = message content. Subject = optional embed title.
Metadata keys:
| Key | Description |
|---|---|
username |
Override the webhook display name |
avatar_url |
Override the webhook avatar image URL |
tts |
"true" to send as text-to-speech |
embeds |
JSON string of Discord embed objects |
Native bulk: ❌ No — one message per webhook call.
Microsoft Teams
dotnet add package RecurPixel.Notify.Teams
"Teams": {
"WebhookUrl": "https://outlook.office.com/webhook/xxxxxxxx@xxxxxxxx/IncomingWebhook/xxxxxxxxxx/xxxxxxxxxx"
}
| Field | Required | Description |
|---|---|---|
WebhookUrl |
✅ | Incoming webhook connector URL |
Payload fields: Subject = card title. Body = card text content.
Metadata keys:
| Key | Description |
|---|---|
theme_color |
Hex colour for the card accent bar (e.g. 0076D7) |
card_json |
Full Adaptive Card JSON string (overrides Subject/Body) |
Native bulk: ❌ No — one message per webhook call.
Mattermost
dotnet add package RecurPixel.Notify.Mattermost
"Mattermost": {
"WebhookUrl": "https://mattermost.yourapp.com/hooks/xxxxxxxxxx",
"Username": "NotifyBot",
"Channel": "town-square"
}
| Field | Required | Description |
|---|---|---|
WebhookUrl |
✅ | Incoming webhook URL from Mattermost integrations |
Username |
❌ | Override the bot display name |
Channel |
❌ | Override the target channel (e.g. town-square) |
Payload fields: Subject = bold header. Body = message text. Formatted as Markdown.
Native bulk: ❌ No — one message per webhook call.
Rocket.Chat
dotnet add package RecurPixel.Notify.RocketChat
"RocketChat": {
"WebhookUrl": "https://rocketchat.yourapp.com/hooks/xxxxxxxxxx",
"Username": "NotifyBot",
"Channel": "#general"
}
| Field | Required | Description |
|---|---|---|
WebhookUrl |
✅ | Incoming webhook URL from Rocket.Chat administration |
Username |
❌ | Override the bot display name |
Channel |
❌ | Override the target channel (e.g. #general) |
Payload fields: Subject = bold header. Body = message text. Formatted as Markdown.
Native bulk: ❌ No — one message per webhook call.
Social & Messaging
Telegram
dotnet add package RecurPixel.Notify.Telegram
"Telegram": {
"BotToken": "123456789:ABCdefGHIjklMNOpqrsTUVwxyz",
"ChatId": "-1001234567890"
}
| Field | Required | Description |
|---|---|---|
BotToken |
✅ | Telegram Bot API token from @BotFather |
ChatId |
✅ | Default target chat/group/channel ID |
Payload fields: Body = message text. HTML and Markdown supported via Metadata.
Metadata keys:
| Key | Description |
|---|---|
chat_id |
Override the default ChatId for this message |
parse_mode |
"HTML" or "MarkdownV2" |
disable_notification |
"true" to send silently |
reply_to_message_id |
Message ID to reply to |
Native bulk: ❌ No — per-user DMs loop. Channel broadcasts use a single chat_id.
Facebook Messenger
dotnet add package RecurPixel.Notify.Facebook
"Facebook": {
"PageAccessToken": "EAAxxxxxxxxxxxxx",
"ApiVersion": "v18.0"
}
| Field | Required | Description |
|---|---|---|
PageAccessToken |
✅ | Facebook Page access token |
ApiVersion |
❌ | Graph API version (default: v18.0) |
Payload fields: To = recipient’s Facebook page-scoped user ID (PSID). Body = message text.
Native bulk: ❌ No — Messenger API is per-user. Base class loops.
LINE
dotnet add package RecurPixel.Notify.Line
"Line": {
"ChannelAccessToken": "xxxxxxxxxx",
"ChannelSecret": "xxxxxxxxxx"
}
| Field | Required | Description |
|---|---|---|
ChannelAccessToken |
✅ | LINE Messaging API channel access token |
ChannelSecret |
✅ | LINE channel secret |
Payload fields: To = LINE user ID. Body = message text.
Native bulk: ❌ No — per-user messages loop. Use LINE broadcast endpoint for channel-wide pushes via Metadata.
Viber
dotnet add package RecurPixel.Notify.Viber
"Viber": {
"AuthToken": "xxxxxxxxxx",
"SenderName": "Your App",
"SenderAvatar": "https://yourapp.com/avatar.png"
}
| Field | Required | Description |
|---|---|---|
AuthToken |
✅ | Viber service auth token |
SenderName |
✅ | Sender display name (max 28 characters) |
SenderAvatar |
❌ | URL to sender avatar image |
Payload fields: To = Viber user ID. Body = message text.
Native bulk: ❌ No — base class loops.
In-App
InApp (Hook-Based)
dotnet add package RecurPixel.Notify.InApp
The InApp channel does not call an external API. It calls your hook with the payload — you define what “in-app delivery” means for your system.
"InApp": {
"Enabled": true
}
options.InApp.OnDeliver(async payload =>
{
await db.InAppNotifications.AddAsync(new InAppNotification
{
UserId = payload.Metadata.GetValueOrDefault("user_id")?.ToString(),
Title = payload.Subject,
Body = payload.Body,
CreatedAt = DateTime.UtcNow,
IsRead = false
});
await db.SaveChangesAsync();
});
You own the schema, the table, and the read/unread logic. We call your hook.
Native Bulk Support Summary
| Channel | Provider | Native Bulk | Limit |
|---|---|---|---|
| SendGrid | ✅ | 1,000/call | |
| AwsSes | ✅ | Batch API | |
| Postmark | ✅ | Batch endpoint | |
| Mailgun | ✅ | Recipient variables | |
| AzureCommEmail | ❌ | Loop | |
| Resend | ❌ | Loop | |
| SMTP | ❌ | Loop | |
| SMS | Twilio | ❌ | Loop |
| SMS | Vonage | ✅ | Bulk SMS API |
| SMS | AwsSns | ✅ | Topic publish |
| SMS | AzureCommSms | ✅ | 100/call |
| SMS | Sinch | ✅ | Batch SMS API |
| SMS | MSG91 | ✅ | Batch SMS API |
| SMS | Plivo | ❌ | Loop |
| SMS | MessageBird | ❌ | Loop |
| Push | FCM | ✅ | 500 tokens/call |
| Push | APNs | ❌ | Loop |
| Push | OneSignal | ✅ | Bulk API |
| Push | Expo | ✅ | Batch tickets |
| Twilio, MetaCloud, Vonage, MSG91 | ❌ | Loop (Meta policy) | |
| Slack | — | ❌ | Loop |
| Discord | — | ❌ | Loop |
| Teams | — | ❌ | Loop |
| Mattermost | — | ❌ | Loop |
| Rocket.Chat | — | ❌ | Loop |
| Telegram | — | ❌ | Loop (DMs) |
| — | ❌ | Loop | |
| LINE | — | ❌ | Loop |
| Viber | — | ❌ | Loop |
Adapters without native bulk use the base class loop with a configurable concurrency cap (default: 10 concurrent). Configure via BulkOptions.ConcurrencyLimit in appsettings.json.