6.8 CommunicationController
Route: api/v1/Communication · Class attributes: [Authorize] · 12 endpoints — the ERP's
channel-agnostic send API (Email/WhatsApp/SMS/Push/In-App/Document). WhatsApp is one of five channels; every
endpoint is documented here for completeness, with Meta interaction called out explicitly where relevant.
POST Send/Email
Purpose: send an email via the tenant's configured SMTP, recording CommunicationHistory.
Permission: Communications.SendEmail · Request (SendEmailRequest):
{ "recipient": { "type": 0, "email": "customer@example.com", "name": "Ahmed" },
"subject": "Your Invoice", "body": "<p>Please find attached...</p>", "isHtml": true,
"referenceType": "Invoice", "referenceId": "inv-...", "referenceNumber": "INV-2026-00451" }
Response: Result<Guid> (the CommunicationHistory.Id). Backend: CommunicationService.SendEmailAsync.
Calls Meta API: No.
POST Send/WhatsApp
Purpose: send a WhatsApp template message via the tenant's own WhatsApp Business number.
Permission: Communications.SendWhatsApp · Request (SendWhatsAppRequest):
{ "recipient": { "type": 0, "phone": "966501112222", "name": "Ahmed", "languageCode": "ar" },
"eventKey": "InvoiceIssued", "templateName": "send_document_invoice",
"data": { "customerName": "Ahmed", "amount": "12500.00", "invoiceNumber": "INV-2026-00451" },
"referenceType": "Invoice", "referenceId": "inv-...", "referenceNumber": "INV-2026-00451" }
Response: Result<Guid>.
Error cases: tenant WhatsApp not configured/enabled (WhatsAppOperationalSettings.EnableSending == false)
→ Result<Guid>.FailureAsync with a configuration error message, not an HTTP error; phone normalization
failure → same failure shape; Meta rejection → failure message contains Meta's error code/message.
Backend: CommunicationService.SendWhatsAppAsync → ITenantWhatsAppSender.SendTemplateAsync.
Calls Meta API: Yes.
Angular/Flutter: any tenant-triggered "send this document by WhatsApp" action (invoices, quotes, receipts).
POST Send/Sms
Purpose: queue an SMS via the platform Notification Framework. Permission: Communications.SendSms
Request (SendSmsRequest): { "recipient": {...}, "messageText": "Your OTP is 482913", "referenceType": "Otp" }
Response: Result<Guid>. Backend: CommunicationService.SendSmsAsync. Calls Meta API: No.
POST Send/Push
Purpose: queue a push notification. Permission: Communications.SendPush
Request (SendPushRequest): { "recipient": {...}, "title": "Order Shipped", "body": "...", "eventKey": "OrderShipped" }
Response: Result<Guid>. Backend: CommunicationService.SendPushAsync. Calls Meta API: No.
POST Send/Notification
Purpose: queue an in-app notification. Permission: Communications.Send
Request (SendNotificationRequest): { "recipient": {...}, "eventKey": "PurchaseInvoicePosted", "data": {...} }
Response: Result<Guid>. Backend: CommunicationService.SendNotificationAsync. Calls Meta API: No.
POST Send/Document
Purpose: channel-routing dispatcher for business documents — the caller picks a Channel and this
endpoint routes to the correct underlying send.
Permission: Communications.SendDocument · Request (SendDocumentRequest):
{ "channel": 2, "recipient": { "phone": "966501112222", "languageCode": "ar" },
"documentType": "Invoice", "documentId": "inv-...", "documentNumber": "INV-2026-00451",
"templateName": "send_document_invoice", "data": { "amount": "12500.00" } }
(channel: 2 = WhatsApp, per the CommunicationChannel enum used by this module —
Email=1, WhatsApp=2, Sms=3, Push=4, InApp=5. Note this is a different, narrower enum than the
Notification Framework's own NotificationChannel — Email=1, Sms=2, Push=3, InApp=4, SignalR=5, WhatsApp=6
— used by AppNotificationTemplate.Channel and documented in the
Notification Framework's channel chapter. Do
not confuse the two when integrating.)
Response: Result<Guid>. Backend: CommunicationService.SendDocumentAsync, which internally calls
the same channel-specific method as the dedicated Send/{Channel} endpoint.
Calls Meta API: Yes, but only when Channel == WhatsApp.
POST Preview
Purpose: preview the resolved recipient/template/body a Send/Document call would produce, without
actually sending. Permission: Communications.Preview · Request: same shape as Send/Document.
Response (Result<CommunicationPreviewResult>):
{ "channel": 2, "subject": null, "body": "مرحباً أحمد، فاتورتك رقم INV-2026-00451 بمبلغ 12500.00 ر.س جاهزة.",
"recipientContact": "966501112222", "templateName": "send_document_invoice", "languageCode": "ar", "isValid": true }
Backend: CommunicationService.PreviewAsync. Calls Meta API: No.
Angular/Flutter: a "Preview before sending" step in any document-send UI.
GET ValidateSettings/{channel}
Purpose: validate one channel's tenant configuration. Permission: Communications.ManageSettings
Request: route channel:CommunicationChannel (e.g. 2 for WhatsApp).
Response (Result<CommunicationValidationResult>): { "isValid": false, "errors": ["AccessToken is not configured"], "warnings": [] }
Backend: CommunicationService.ValidateSettingsAsync. Calls Meta API: No.
GET ProviderInfo/Email · GET ProviderInfo/WhatsApp
Purpose: resolved provider info for a channel (which provider is active, whether configured/enabled).
Permission: Communications.ManageSettings · Response data:
{ "source": 1, "providerName": "Meta WhatsApp Cloud API", "isConfigured": true, "isEnabled": true, "senderIdentity": "966501234567", "validationErrors": [], "validationWarnings": [] }
Backend: CommunicationService.GetEmailProviderInfoAsync / GetWhatsAppProviderInfoAsync.
Calls Meta API: No.
POST Test/Email
Purpose: test SMTP connectivity by sending a real test email. Permission: Communications.TestEmail
Request (TestEmailRequest): { "toEmail": "admin@tenant.com", "subject": "Shumoul Test Email" }
Response: Result<Guid>. Backend: CommunicationService.TestEmailAsync. Calls Meta API: No.
POST Test/WhatsApp
Purpose: test WhatsApp API connectivity via a business-account lookup (not a message send).
Permission: Communications.TestWhatsApp · Request (TestWhatsAppRequest):
{ "toPhone": "966501112222", "templateName": "hello_world", "languageCode": "ar" }
Response: Result<Guid>.
Error cases: invalid/expired access token → failure message containing Meta's HTTP status + raw body.
Backend: CommunicationService.TestWhatsAppAsync → ITenantWhatsAppSender.TestConnectionAsync — a
direct GET {BaseUrl}/{GraphApiVersion}/{BusinessAccountId}?fields=id,name call.
Calls Meta API: Yes.
Angular/Flutter: a "Test Connection" button on the tenant's WhatsApp settings screen.
