RepletoryLib.Communication.Abstractions
Communication abstractions and interfaces for email, SMS, WhatsApp, and push notifications with a unified routing facade.
Part of the RepletoryLib ecosystem -- standalone, reusable .NET 10 libraries with zero business logic.

Overview
RepletoryLib.Communication.Abstractions defines the contracts, models, enums, and options shared across all RepletoryLib communication channels. It includes interfaces for email (IEmailService), SMS (ISmsService), WhatsApp (IWhatsAppService), and push notifications (IPushNotificationService), along with a unified ICommunicationService facade that routes messages to the appropriate channel-specific service at runtime.
This package is the foundation of the communication stack. Reference it when you need the interfaces or models without taking a dependency on a specific provider (e.g., AWS SES, AWS SNS, 360Dialog).
Key Features
IEmailService -- Send single emails, bulk emails, and templated emails
ISmsService -- Send single and bulk SMS messages
IWhatsAppService -- Send text, template, and media messages via WhatsApp
IPushNotificationService -- Send device-targeted and topic-based push notifications
ICommunicationService -- Unified facade that routes CommunicationMessage to the correct channel
CommunicationService -- Default routing implementation with optional channel services resolved from DI
- Rich models --
EmailMessage, SmsMessage, WhatsAppTextMessage, PushNotification, and more
- Result types --
SendResult and BulkSendResult with per-message success/failure tracking
- Enums --
CommunicationChannel, PushProvider, WhatsAppMediaType, SmsType
Installation
dotnet add package RepletoryLib.Communication.Abstractions
Or add to your .csproj:
<PackageReference Include="RepletoryLib.Communication.Abstractions" Version="1.0.0" />
Note: RepletoryLib packages are published to a local BaGet feed.
See the main repository README for feed configuration.
Dependencies
| Package |
Type |
RepletoryLib.Common |
RepletoryLib |
Quick Start
using RepletoryLib.Communication.Abstractions.Interfaces;
using RepletoryLib.Communication.Abstractions.Models.Common;
using RepletoryLib.Communication.Abstractions.Enums;
public class NotificationService
{
private readonly ICommunicationService _communication;
public NotificationService(ICommunicationService communication) => _communication = communication;
public async Task<SendResult> NotifyUserAsync(string email, string message)
{
return await _communication.SendAsync(new CommunicationMessage
{
Channel = CommunicationChannel.Email,
Recipient = email,
Subject = "Notification",
Body = message
});
}
}
Configuration
CommunicationOptions
| Property |
Type |
Default |
Description |
DefaultFromEmail |
string |
"" |
Default sender email address |
DefaultFromName |
string |
"" |
Default sender display name |
DefaultSmsSenderId |
string |
"" |
Default SMS sender ID |
BulkBatchSize |
int |
50 |
Batch size for bulk send operations |
RetryCount |
int |
3 |
Number of retry attempts for failed sends |
Section name: "Communication"
{
"Communication": {
"DefaultFromEmail": "noreply@example.com",
"DefaultFromName": "My Application",
"DefaultSmsSenderId": "MyApp",
"BulkBatchSize": 50,
"RetryCount": 3
}
}
Usage Examples
Sending via the Unified Facade
using RepletoryLib.Communication.Abstractions.Enums;
using RepletoryLib.Communication.Abstractions.Interfaces;
using RepletoryLib.Communication.Abstractions.Models.Common;
public class AlertService
{
private readonly ICommunicationService _communication;
public AlertService(ICommunicationService communication) => _communication = communication;
// Send an email
public async Task SendEmailAlertAsync(string email, string subject, string htmlBody)
{
var result = await _communication.SendAsync(new CommunicationMessage
{
Channel = CommunicationChannel.Email,
Recipient = email,
Subject = subject,
Body = htmlBody
});
if (!result.Success)
throw new InvalidOperationException($"Email failed: {result.Error}");
}
// Send an SMS
public async Task SendSmsAlertAsync(string phoneNumber, string message)
{
var result = await _communication.SendAsync(new CommunicationMessage
{
Channel = CommunicationChannel.Sms,
Recipient = phoneNumber,
Body = message
});
}
// Send a WhatsApp message
public async Task SendWhatsAppAlertAsync(string phoneNumber, string message)
{
var result = await _communication.SendAsync(new CommunicationMessage
{
Channel = CommunicationChannel.WhatsApp,
Recipient = phoneNumber,
Body = message
});
}
// Send a push notification
public async Task SendPushAlertAsync(string deviceToken, string title, string body)
{
var result = await _communication.SendAsync(new CommunicationMessage
{
Channel = CommunicationChannel.Push,
Recipient = deviceToken,
Subject = title,
Body = body,
Metadata = new Dictionary<string, string> { ["action"] = "open_alerts" }
});
}
}
Bulk Send Across Multiple Channels
using RepletoryLib.Communication.Abstractions.Enums;
using RepletoryLib.Communication.Abstractions.Interfaces;
using RepletoryLib.Communication.Abstractions.Models.Common;
public class BroadcastService
{
private readonly ICommunicationService _communication;
public BroadcastService(ICommunicationService communication) => _communication = communication;
public async Task BroadcastAsync(
string subject,
string body,
IEnumerable<(CommunicationChannel Channel, string Recipient)> targets)
{
var messages = targets.Select(t => new CommunicationMessage
{
Channel = t.Channel,
Recipient = t.Recipient,
Subject = subject,
Body = body
});
var result = await _communication.SendBulkAsync(messages);
Console.WriteLine($"Sent: {result.SuccessCount}, Failed: {result.FailureCount}");
}
}
Using Channel-Specific Services Directly
using RepletoryLib.Communication.Abstractions.Interfaces;
using RepletoryLib.Communication.Abstractions.Models.Email;
public class WelcomeEmailService
{
private readonly IEmailService _emailService;
public WelcomeEmailService(IEmailService emailService) => _emailService = emailService;
public async Task SendWelcomeAsync(string email, string name)
{
var result = await _emailService.SendAsync(new EmailMessage
{
To = [email],
Subject = $"Welcome, {name}!",
HtmlBody = $"<h1>Hello {name}</h1><p>Thanks for joining us.</p>",
TextBody = $"Hello {name}\n\nThanks for joining us."
});
if (!result.Success)
throw new InvalidOperationException($"Welcome email failed: {result.Error}");
}
public async Task SendTemplatedWelcomeAsync(string email, string name)
{
var result = await _emailService.SendTemplateAsync(new TemplatedEmailMessage
{
To = [email],
TemplateName = "WelcomeTemplate",
TemplateData = new Dictionary<string, string>
{
["name"] = name,
["year"] = DateTime.UtcNow.Year.ToString()
}
});
}
}
Working with SendResult and BulkSendResult
using RepletoryLib.Communication.Abstractions.Enums;
using RepletoryLib.Communication.Abstractions.Models.Common;
// SendResult factory methods
var success = SendResult.Succeeded(CommunicationChannel.Email, "msg-12345");
var failure = SendResult.Failed(CommunicationChannel.Sms, "Rate limit exceeded");
// Inspect the result
if (success.Success)
Console.WriteLine($"Message sent: {success.MessageId}");
if (!failure.Success)
Console.WriteLine($"Send failed on {failure.Channel}: {failure.Error}");
// BulkSendResult aggregation
var bulk = new BulkSendResult
{
Results = [success, failure]
};
Console.WriteLine($"Total: {bulk.Results.Count}, OK: {bulk.SuccessCount}, Failed: {bulk.FailureCount}");
API Reference
Interfaces
IEmailService
| Method |
Returns |
Description |
SendAsync(EmailMessage, ct) |
Task<SendResult> |
Sends a single email message |
SendBulkAsync(IEnumerable<EmailMessage>, ct) |
Task<BulkSendResult> |
Sends multiple emails with per-message results |
SendTemplateAsync(TemplatedEmailMessage, ct) |
Task<SendResult> |
Sends an email using a provider-hosted template |
ISmsService
| Method |
Returns |
Description |
SendAsync(SmsMessage, ct) |
Task<SendResult> |
Sends a single SMS message |
SendBulkAsync(IEnumerable<SmsMessage>, ct) |
Task<BulkSendResult> |
Sends multiple SMS messages with per-message results |
IWhatsAppService
| Method |
Returns |
Description |
SendTextAsync(WhatsAppTextMessage, ct) |
Task<SendResult> |
Sends a text message via WhatsApp |
SendTemplateAsync(WhatsAppTemplateMessage, ct) |
Task<SendResult> |
Sends a pre-approved template message via WhatsApp |
SendMediaAsync(WhatsAppMediaMessage, ct) |
Task<SendResult> |
Sends a media message (image, video, audio, document) via WhatsApp |
SendBulkAsync(IEnumerable<WhatsAppTextMessage>, ct) |
Task<BulkSendResult> |
Sends multiple text messages with per-message results |
IPushNotificationService
| Method |
Returns |
Description |
SendToDeviceAsync(PushNotification, ct) |
Task<SendResult> |
Sends a push notification to a specific device |
SendToTopicAsync(TopicPushNotification, ct) |
Task<SendResult> |
Sends a push notification to all subscribers of a topic |
SendBulkAsync(IEnumerable<PushNotification>, ct) |
Task<BulkSendResult> |
Sends multiple push notifications with per-message results |
ICommunicationService
| Method |
Returns |
Description |
SendAsync(CommunicationMessage, ct) |
Task<SendResult> |
Routes and sends a message via the specified channel |
SendBulkAsync(IEnumerable<CommunicationMessage>, ct) |
Task<BulkSendResult> |
Routes and sends multiple messages across potentially different channels |
Models
EmailMessage
| Property |
Type |
Required |
Description |
To |
List<string> |
Yes |
Primary recipient email addresses |
Cc |
List<string>? |
No |
CC recipient email addresses |
Bcc |
List<string>? |
No |
BCC recipient email addresses |
Subject |
string |
Yes |
Email subject line |
HtmlBody |
string? |
No |
HTML body content |
TextBody |
string? |
No |
Plain text body content |
ReplyTo |
string? |
No |
Reply-to email address |
Attachments |
List<EmailAttachment>? |
No |
File attachments |
Headers |
Dictionary<string, string>? |
No |
Custom email headers |
TemplatedEmailMessage
| Property |
Type |
Required |
Description |
To |
List<string> |
Yes |
Primary recipient email addresses |
TemplateName |
string |
Yes |
Template name or identifier |
TemplateData |
Dictionary<string, string> |
No |
Template data for placeholder substitution |
Cc |
List<string>? |
No |
CC recipient email addresses |
Bcc |
List<string>? |
No |
BCC recipient email addresses |
EmailAttachment
| Property |
Type |
Required |
Description |
FileName |
string |
Yes |
Attachment file name |
ContentType |
string |
Yes |
MIME content type |
Content |
byte[] |
Yes |
Attachment content as byte array |
SmsMessage
| Property |
Type |
Required |
Description |
To |
string |
Yes |
Recipient phone number in E.164 format |
Body |
string |
Yes |
Message body text |
SenderId |
string? |
No |
Sender ID displayed on recipient's device |
WhatsAppTextMessage
| Property |
Type |
Required |
Description |
To |
string |
Yes |
Recipient phone number in E.164 format |
Body |
string |
Yes |
Message body text |
WhatsAppTemplateMessage
| Property |
Type |
Required |
Description |
To |
string |
Yes |
Recipient phone number in E.164 format |
TemplateName |
string |
Yes |
Template name as registered with WhatsApp Business |
LanguageCode |
string |
Yes |
Language code (e.g., "en", "en_US") |
Parameters |
List<string>? |
No |
Template parameter values for placeholder substitution |
| Property |
Type |
Required |
Description |
To |
string |
Yes |
Recipient phone number in E.164 format |
MediaType |
WhatsAppMediaType |
Yes |
Type of media (Image, Video, Audio, Document) |
MediaUrl |
string |
Yes |
URL of the media to send |
Caption |
string? |
No |
Optional caption for the media |
PushNotification
| Property |
Type |
Required |
Description |
DeviceToken |
string |
Yes |
Device token or registration ID |
Title |
string |
Yes |
Notification title |
Body |
string |
Yes |
Notification body text |
Data |
Dictionary<string, string>? |
No |
Custom data payload |
ImageUrl |
string? |
No |
Image URL for rich notifications |
Badge |
int? |
No |
Badge count on app icon |
Sound |
string? |
No |
Notification sound name |
ChannelId |
string? |
No |
Android notification channel ID |
Provider |
PushProvider |
No |
Push provider to use (Firebase, Expo, WebPush) |
TopicPushNotification
| Property |
Type |
Required |
Description |
Topic |
string |
Yes |
Topic name to send the notification to |
Title |
string |
Yes |
Notification title |
Body |
string |
Yes |
Notification body text |
Data |
Dictionary<string, string>? |
No |
Custom data payload |
Provider |
PushProvider |
No |
Push provider to use |
CommunicationMessage
| Property |
Type |
Required |
Description |
Channel |
CommunicationChannel |
Yes |
Communication channel to use |
Recipient |
string |
Yes |
Recipient address (email, phone, device token) |
Subject |
string |
No |
Message subject (email subject / push title) |
Body |
string |
Yes |
Message body |
Metadata |
Dictionary<string, string>? |
No |
Optional metadata for channel-specific features |
SendResult
| Property |
Type |
Description |
Success |
bool |
Whether the send operation succeeded |
MessageId |
string? |
Provider-assigned message identifier |
Error |
string? |
Error message if the send failed |
Channel |
CommunicationChannel |
Communication channel used |
| Static Method |
Returns |
Description |
Succeeded(channel, messageId?) |
SendResult |
Creates a successful result |
Failed(channel, error) |
SendResult |
Creates a failed result |
BulkSendResult
| Property |
Type |
Description |
Results |
List<SendResult> |
Individual results for each message |
SuccessCount |
int |
Number of successfully sent messages |
FailureCount |
int |
Number of failed messages |
Enums
CommunicationChannel
| Value |
Description |
Email |
Email via SMTP or cloud email service |
Sms |
SMS text message |
WhatsApp |
WhatsApp messaging |
Push |
Push notification to mobile or web devices |
PushProvider
| Value |
Description |
Firebase |
Firebase Cloud Messaging (FCM) |
Expo |
Expo Push Notification service |
WebPush |
Web Push using VAPID protocol |
| Value |
Description |
Image |
Image file (JPEG, PNG) |
Video |
Video file (MP4) |
Audio |
Audio file (MP3, OGG) |
Document |
Document file (PDF, DOCX) |
SmsType
| Value |
Description |
Transactional |
Transactional SMS (OTP, alerts, confirmations). Higher delivery priority |
Promotional |
Promotional SMS (marketing, offers). Subject to opt-in regulations |
Integration with Other RepletoryLib Packages
| Package |
Relationship |
RepletoryLib.Common |
Direct dependency -- base models and utilities |
RepletoryLib.Communication.Email |
AWS SES implementation of IEmailService |
RepletoryLib.Communication.Sms |
AWS SNS implementation of ISmsService |
RepletoryLib.Communication.WhatsApp |
360Dialog implementation of IWhatsAppService |
RepletoryLib.Communication.Push |
Firebase/Expo/WebPush implementation of IPushNotificationService |
Testing
using Moq;
using RepletoryLib.Communication.Abstractions.Enums;
using RepletoryLib.Communication.Abstractions.Interfaces;
using RepletoryLib.Communication.Abstractions.Models.Common;
using RepletoryLib.Communication.Abstractions.Models.Email;
using RepletoryLib.Communication.Abstractions.Services;
[Fact]
public async Task CommunicationService_routes_email_to_email_service()
{
var mockEmail = new Mock<IEmailService>();
mockEmail
.Setup(e => e.SendAsync(It.IsAny<EmailMessage>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(SendResult.Succeeded(CommunicationChannel.Email, "msg-001"));
var service = new CommunicationService(emailService: mockEmail.Object);
var result = await service.SendAsync(new CommunicationMessage
{
Channel = CommunicationChannel.Email,
Recipient = "user@example.com",
Subject = "Test",
Body = "<p>Hello</p>"
});
result.Success.Should().BeTrue();
result.MessageId.Should().Be("msg-001");
result.Channel.Should().Be(CommunicationChannel.Email);
mockEmail.Verify(e => e.SendAsync(
It.Is<EmailMessage>(m => m.To.Contains("user@example.com")),
It.IsAny<CancellationToken>()), Times.Once);
}
[Fact]
public async Task CommunicationService_returns_failure_when_channel_not_registered()
{
var service = new CommunicationService(); // No services registered
var result = await service.SendAsync(new CommunicationMessage
{
Channel = CommunicationChannel.Sms,
Recipient = "+27821234567",
Body = "Hello"
});
result.Success.Should().BeFalse();
result.Error.Should().Contain("not registered");
}
Troubleshooting
| Issue |
Solution |
SendResult.Error says "service is not registered" |
The channel-specific service (e.g., IEmailService) was not added to DI. Register the implementation package (e.g., AddRepletoryEmail) |
BulkSendResult shows partial failures |
Inspect individual Results entries -- bulk operations continue on per-message failure |
CommunicationMessage with wrong Channel |
Ensure the Channel enum matches the recipient type (email address, phone number, device token) |
ArgumentNullException on send |
Ensure the message object is not null before passing it to send methods |
License
This project is licensed under the MIT License.
Copyright (c) 2024-2026 Repletory.
For complete documentation, infrastructure setup, and configuration reference,
see the RepletoryLib main repository.