![]() |
VOOZH | about |
dotnet add package Acontplus.Notifications --version 1.6.5
NuGet\Install-Package Acontplus.Notifications -Version 1.6.5
<PackageReference Include="Acontplus.Notifications" Version="1.6.5" />
<PackageVersion Include="Acontplus.Notifications" Version="1.6.5" />Directory.Packages.props
<PackageReference Include="Acontplus.Notifications" />Project file
paket add Acontplus.Notifications --version 1.6.5
#r "nuget: Acontplus.Notifications, 1.6.5"
#:package Acontplus.Notifications@1.6.5
#addin nuget:?package=Acontplus.Notifications&version=1.6.5Install as a Cake Addin
#tool nuget:?package=Acontplus.Notifications&version=1.6.5Install as a Cake Tool
๐ NuGet
๐ .NET
๐ License: MIT
Production-ready multi-channel notification library for .NET 10. Covers email (MailKit SMTP + Amazon SES) and WhatsApp Cloud API (Meta Graph API v23.0) in a single package โ templating, media, interactive messages, multi-tenant credentials, resilience, and webhook validation included.
dotnet add package Acontplus.Notifications
<PackageReference Include="Acontplus.Notifications" Version="1.6.2" />
// Program.cs
builder.Services.AddWhatsAppService(builder.Configuration);
// or inline:
builder.Services.AddWhatsAppService(opts =>
{
opts.PhoneNumberId = "1234567890";
opts.AccessToken = "EAAxxxx...";
opts.ApiVersion = "v23.0";
opts.DefaultCountryCode = "593"; // 09xxxxxxxx โ 593 9xxxxxxxx
});
{
"WhatsApp": {
// REQUIRED (when using server-stored credentials)
"PhoneNumberId": "YOUR_PHONE_NUMBER_ID",
"AccessToken": "YOUR_PERMANENT_SYSTEM_USER_TOKEN",
// OPTIONAL โ defaults shown
"ApiVersion": "v23.0",
"TimeoutSeconds": 30,
// OPTIONAL โ auto-prefix numbers starting with "0" (e.g. 09xxxxxxxx โ 5939xxxxxxxx)
"DefaultCountryCode": "593",
// OPTIONAL โ only needed if you call ValidateWebhookSignature()
"AppSecret": "YOUR_APP_SECRET_FOR_WEBHOOK_VALIDATION",
// OPTIONAL โ only needed if you handle Meta's GET webhook verify handshake
"WebhookVerifyToken": "YOUR_WEBHOOK_VERIFY_TOKEN",
// OPTIONAL โ multi-tenant: named accounts override the default credentials above
"Accounts": {
"company-a": {
"PhoneNumberId": "COMPANY_A_PHONE_ID",
"AccessToken": "COMPANY_A_TOKEN"
}
}
}
}
Per-request credentials: If your callers supply
AccessToken/PhoneNumberIdon every request (e.g. credentials come from the frontend), you can omitPhoneNumberIdandAccessTokenentirely from appsettings and only keepTimeoutSeconds.Obtain credentials: Meta Business Manager โ WhatsApp โ API Setup. Use a permanent system-user token (never expires) instead of a temporary one.
public class NotificationService(IWhatsAppService whatsApp)
{
// Text message (requires 24-hour conversation window)
public Task<WhatsAppResult> SendTextAsync(string to, string body) =>
whatsApp.SendTextAsync(new() { To = to, Body = body });
// Template (works any time โ no 24-hour window needed)
public Task<WhatsAppResult> SendInvoiceAsync(string to, string mediaId, string customerName, string invoiceNumber) =>
whatsApp.SendTemplateAsync(new()
{
To = to,
TemplateName = "notificacion_documento",
LanguageCode = "es_EC",
Components =
[
WhatsAppTemplateComponent.HeaderDocument(mediaId, "factura.pdf", isId: true),
WhatsAppTemplateComponent.Body(customerName, invoiceNumber)
]
});
// Upload a file once โ reuse the ID
public async Task<string?> UploadInvoiceAsync(Stream pdfStream) =>
await whatsApp.UploadMediaAsync(new()
{
FileStream = pdfStream,
FileName = "invoice.pdf",
ContentType = "application/pdf"
});
// Interactive quick-reply buttons
public Task<WhatsAppResult> SendConfirmationAsync(string to) =>
whatsApp.SendInteractiveAsync(new()
{
To = to,
InteractiveType = WhatsAppInteractiveType.Button,
BodyText = "Do you confirm your appointment?",
ReplyButtons =
[
new WhatsAppReplyButton { Id = "yes", Title = "Yes, confirm" },
new WhatsAppReplyButton { Id = "no", Title = "No, cancel" }
]
});
// Multi-tenant: named account
public Task<WhatsAppResult> SendForCompanyAsync(string to, string message) =>
whatsApp.SendTextAsync(new()
{
To = to,
Body = message,
Credentials = WhatsAppCredentials.FromAccount("company-a")
});
}
app.MapPost("/webhook/whatsapp", async (
HttpRequest req,
IWhatsAppService whatsApp) =>
{
var signature = req.Headers["X-Hub-Signature-256"].ToString();
var body = await new StreamReader(req.Body).ReadToEndAsync();
if (!whatsApp.ValidateWebhookSignature(signature, body))
return Results.Unauthorized();
// process payload ...
return Results.Ok();
});
// Webhook verification handshake (GET)
app.MapGet("/webhook/whatsapp", (
[FromQuery(Name = "hub.mode")] string mode,
[FromQuery(Name = "hub.verify_token")] string token,
[FromQuery(Name = "hub.challenge")] string challenge,
IConfiguration config) =>
{
var expected = config["WhatsApp:WebhookVerifyToken"];
return mode == "subscribe" && token == expected
? Results.Ok(int.Parse(challenge))
: Results.Unauthorized();
});
| Method | Type | 24-h window? |
|---|---|---|
SendTextAsync |
Plain text with optional preview | Required |
SendTemplateAsync |
Pre-approved template (text + media header, buttons) | Not required |
SendMediaAsync |
Image / Document / Audio / Video / Sticker | Required |
SendLocationAsync |
Map pin | Required |
SendInteractiveAsync |
Quick-reply buttons / List menu / CTA URL | Required |
SendReactionAsync |
Emoji reaction on a received message | Required |
MarkAsReadAsync |
Read receipt (double blue ticks) | โ |
UploadMediaAsync |
Upload file โ get reusable media ID | โ |
ValidateWebhookSignature |
HMAC-SHA256 webhook verification | โ |
WhatsAppCredentials.FromAccount("name") โ looks up WhatsApp:Accounts:nameWhatsAppCredentials.Inline(phoneId, token)WhatsApp:PhoneNumberId + WhatsApp:AccessToken// Program.cs
builder.Services.AddMemoryCache(); // enables template caching
builder.Services.AddSingleton<IMailKitService, AmazonSesService>(); // or MailKitService
{
"AWS": {
"SES": {
"Region": "us-east-1",
"DefaultFromEmail": "noreply@example.com",
"MaxSendRate": 14,
"BatchSize": 50,
"BatchDelayMs": 100,
"AccessKey": "AKIA...",
"SecretKey": "your-secret-key"
}
},
"Templates": { "Path": "Templates" },
"Media": { "ImagesPath": "wwwroot/images" }
}
var success = await emailService.SendAsync(new EmailModel
{
SenderEmail = "noreply@example.com",
RecipientEmail = "user@example.com",
Subject = "Welcome!",
Template = "welcome.html", // cached 30 min
Body = JsonSerializer.Serialize(new { UserName = "John" }),
IsHtml = false
});
var emails = recipients.Select(r => new EmailModel { ... }).ToList();
await emailService.SendBulkAsync(emails, ct); // auto-batched, rate-limited
| Key | Default | Required? | Description |
|---|---|---|---|
PhoneNumberId |
โ | Conditional ยน | WhatsApp Business phone number ID |
AccessToken |
โ | Conditional ยน | Meta Graph API token (system-user recommended) |
ApiVersion |
v23.0 |
Optional | Meta Graph API version |
TimeoutSeconds |
30 |
Optional | Per-attempt HTTP timeout |
DefaultCountryCode |
null |
Optional | Auto-prefix numbers starting with 0 (e.g. "593") |
AppSecret |
null |
Optional ยฒ | App secret for webhook HMAC-SHA256 signature validation |
WebhookVerifyToken |
null |
Optional ยณ | Token for Meta's GET webhook verify handshake |
Accounts |
{} |
Optional | Named accounts for multi-tenant (key โ PhoneNumberId + AccessToken) |
ยน Conditional โ required when using server-stored default credentials. Omit when all callers supply credentials per-request via
WhatsAppCredentials(e.g. credentials come from the frontend). In that case onlyTimeoutSecondsis meaningful.ยฒ Only needed if you call
ValidateWebhookSignature().ยณ Only needed if you handle Meta's GET webhook verify handshake endpoint.
| Key | Default | Description |
|---|---|---|
Region |
us-east-1 |
AWS SES region |
DefaultFromEmail |
โ | Default sender |
MaxSendRate |
14 |
Max emails/second |
BatchSize |
50 |
Recipients per bulk batch |
BatchDelayMs |
100 |
Delay between batches |
| Key | Default | Description |
|---|---|---|
MaxPoolSize |
3 |
SMTP connection pool size |
MinAuthIntervalSeconds |
30 |
Min time between auth attempts |
MaxAuthAttemptsPerHour |
10 |
Auth rate limit |
| Layer | Configuration |
|---|---|
| Retries | 3 attempts, 600 ms base delay (exponential) |
| Per-attempt timeout | 30 s |
| Total timeout | 90 s (all retries included) |
| Circuit breaker | Included via AddStandardResilienceHandler |
| Metric | Without cache | With cache | Improvement |
|---|---|---|---|
| Template load time | 10โ50 ms | <1 ms | 50ร faster |
| Disk I/O (high volume) | Every request | Once per 30 min | 99% reduction |
Task<WhatsAppResult> SendTextAsync(SendWhatsAppTextRequest, CancellationToken);
Task<WhatsAppResult> SendTemplateAsync(SendWhatsAppTemplateRequest, CancellationToken);
Task<WhatsAppResult> SendMediaAsync(SendWhatsAppMediaRequest, CancellationToken);
Task<WhatsAppResult> SendLocationAsync(SendWhatsAppLocationRequest, CancellationToken);
Task<WhatsAppResult> SendInteractiveAsync(SendWhatsAppInteractiveRequest, CancellationToken);
Task<WhatsAppResult> SendReactionAsync(SendWhatsAppReactionRequest, CancellationToken);
Task<bool> MarkAsReadAsync(string messageId, WhatsAppCredentials?, CancellationToken);
Task<string?> UploadMediaAsync(WhatsAppMediaUpload, WhatsAppCredentials?, CancellationToken);
bool ValidateWebhookSignature(string xHubSignature256, string rawBody, string? appSecret);
Task<bool> SendAsync(EmailModel email, CancellationToken ct);
Task<bool> SendBulkAsync(IEnumerable<EmailModel> emails, CancellationToken ct);
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 net10.0 is compatible. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
This package is not used by any NuGet packages.
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.6.5 | 111 | 5/31/2026 |
| 1.6.4 | 110 | 5/17/2026 |
| 1.6.3 | 97 | 5/17/2026 |
| 1.6.2 | 99 | 5/3/2026 |
| 1.6.1 | 119 | 4/16/2026 |
| 1.6.0 | 112 | 4/13/2026 |
| 1.5.12 | 110 | 4/13/2026 |
| 1.5.11 | 136 | 3/30/2026 |
| 1.5.10 | 128 | 3/26/2026 |
| 1.5.9 | 120 | 3/22/2026 |
| 1.5.8 | 163 | 3/17/2026 |
| 1.5.7 | 126 | 3/8/2026 |
| 1.5.6 | 123 | 3/1/2026 |
| 1.5.5 | 120 | 2/22/2026 |
| 1.5.4 | 140 | 1/16/2026 |
| 1.5.3 | 136 | 1/11/2026 |
| 1.5.2 | 228 | 12/25/2025 |
| 1.5.1 | 209 | 12/23/2025 |
| 1.5.0 | 299 | 12/16/2025 |
| 1.4.7 | 172 | 12/11/2025 |
v1.6.0: WhatsApp Cloud API (Meta Graph API v23.0) โ text, templates (full
component support), media (image/document/audio/video/sticker), location, interactive messages
(buttons/list/CTA), reactions, read receipts, media upload, HMAC-SHA256 webhook validation,
and multi-tenant named accounts.
v1.5.0: Added template caching with IMemoryCache (30min sliding expiration)
for improved performance on high-frequency templates. Reduces I/O operations and speeds up
email rendering. Backward compatible - optional IMemoryCache injection in AmazonSesService
constructor.