> For the complete documentation index, see [llms.txt](https://host2host.onibonje.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://host2host.onibonje.com/docs/48-utility-notification-integrations.md).

# Utility & Notification Integrations (Email, SMS, Push)

## 1. Overview

Third-party **utility services** — email, SMS, push, voice, Slack/Teams — are integrated through a **database-driven notification layer**, not hardcoded vendor calls in routes or admin code.

| Concern             | Owner                                                                                            |
| ------------------- | ------------------------------------------------------------------------------------------------ |
| Stable contract     | **`h2h-notification-api`** — `NotificationService`, `NotificationProvider` SPI                   |
| Vendor adapters     | Wrapper JARs per provider (Twilio, SES, SendGrid, Africa's Talking, SMTP, …)                     |
| Routing & templates | **Database** — `notification_provider_def`, `notification_template`, `notification_subscription` |
| Triggering          | **Events** (`event_code`) + direct API for workflows                                             |
| Delivery audit      | `notification_delivery_log`                                                                      |

**Distinction from partner webhooks:**

| Mechanism        | Audience                              | Protocol                | Module                        |
| ---------------- | ------------------------------------- | ----------------------- | ----------------------------- |
| **Webhook**      | Partner ERP / corporate systems       | HTTPS POST JSON         | `h2h-webhook-dispatcher`      |
| **Notification** | Humans (bank staff, partner contacts) | Email, SMS, push, Slack | `h2h-notification-dispatcher` |
| **WebSocket**    | Partner real-time UI                  | WSS                     | `h2h-websocket-ingress`       |

All three can subscribe to the same `event_code` (e.g. `PAYMENT_POSTED`) with different channels.

***

## 2. Module Layout

```
h2h-notification-api/
├── NotificationService.java          # send(), sendBulk()
├── NotificationProvider.java         # SPI — one implementation per vendor/channel
├── NotificationProviderRegistry.java
├── TemplateRenderer.java
├── RecipientResolver.java
└── dto/                              # NotificationRequest, DeliveryResult

h2h-notification-dispatcher/
├── EventNotificationListener.java    # consumes platform events
├── NotificationRouter.java           # subscription → provider → template
├── DefaultNotificationService.java
├── delivery/                         # retry, idempotency, rate limits
└── config/
    └── NotificationAutoConfiguration.java

h2h-notification-aws-ses/           # EMAIL — Amazon SES
h2h-notification-sendgrid/            # EMAIL — SendGrid
h2h-notification-smtp/              # EMAIL — on-prem SMTP relay
h2h-notification-twilio/            # SMS, VOICE — Twilio
h2h-notification-africas-talking/     # SMS — Africa's Talking (multi-country Africa)
h2h-notification-termii/              # SMS — Termii (Nigeria-focused)
h2h-notification-slack/             # SLACK — Slack webhooks / API
h2h-notification-fcm/                 # PUSH — Firebase (optional partner app)
```

**Runtime composition:** `h2h-runtime` includes `h2h-notification-dispatcher` + enabled provider JARs per country pack. Banks swap providers via **database config**, not code changes.

***

## 3. SPI Contract

```java
public interface NotificationProvider {

    String providerCode();              // e.g. TWILIO_NG, SES_EU_WEST

    Set<NotificationChannel> channels(); // EMAIL, SMS, PUSH, VOICE, SLACK

    DeliveryResult send(NotificationRequest request, ProviderContext ctx);
}

public interface NotificationService {

    DeliveryResult send(SendNotificationCommand cmd);

    List<DeliveryResult> sendBulk(List<SendNotificationCommand> cmds);
}

public enum NotificationChannel {
    EMAIL, SMS, PUSH, VOICE, SLACK, TEAMS
}
```

**Injection pattern** (same as `CoreBankingIntegration`):

```java
@Bean
NotificationProviderRegistry registry(List<NotificationProvider> providers) {
    return new DefaultNotificationProviderRegistry(providers);
}
```

`notification_provider_def.provider_code` selects the implementation at runtime.

***

## 4. Database Model

### 4.1 `notification_provider_def`

| Column               | Description                                                |
| -------------------- | ---------------------------------------------------------- |
| `provider_id`        | Unique ID                                                  |
| `provider_code`      | `TWILIO_NG`, `SES_GLOBAL`, `SMTP_ONPREM`                   |
| `channel_type`       | `EMAIL`, `SMS`, `PUSH`, …                                  |
| `vendor`             | `TWILIO`, `AWS_SES`, `SENDGRID`, `AFRICAS_TALKING`, `SMTP` |
| `scope_type`         | `GLOBAL`, `COUNTRY`, `PARTNER`                             |
| `scope_id`           | e.g. `NG` — country-specific SMS gateway                   |
| `connection_params`  | JSON — region, sender ID, from-address, API base URL       |
| `credential_ref`     | Vault path for API key / OAuth secret                      |
| `rate_limit_per_min` | Throttle per provider                                      |
| `enabled`            | Boolean                                                    |
| `priority`           | Failover order when multiple providers match               |

**Example — Nigeria SMS failover:**

| provider\_code       | vendor           | priority | connection\_params              |
| -------------------- | ---------------- | -------- | ------------------------------- |
| `AFRICAS_TALKING_NG` | AFRICAS\_TALKING | 1        | `{"senderId":"HEIRS"}`          |
| `TERMII_NG`          | TERMII           | 2        | `{"senderId":"HEIRS"}`          |
| `TWILIO_NG`          | TWILIO           | 3        | `{"messagingServiceSid":"..."}` |

### 4.2 `notification_template`

| Column             | Description                                            |
| ------------------ | ------------------------------------------------------ |
| `template_id`      | Unique ID                                              |
| `template_code`    | `APPROVAL_PENDING`, `SLA_BREACH`, `PAYMENT_FAILED_OPS` |
| `channel_type`     | Must match provider channel                            |
| `locale`           | `en`, `fr`, `pt`                                       |
| `subject_template` | Mustache/FreeMarker (email subject or SMS prefix)      |
| `body_template`    | HTML (email) or plain text (SMS ≤ 160 chars segments)  |
| `version`          | Template versioning                                    |
| `status`           | DRAFT / PUBLISHED (same maker–checker as config)       |

**SMS example** (`PAYMENT_FAILED_OPS`):

```
H2H Alert: Payment {{correlationId}} failed for {{partnerName}}.
Code: {{errorCode}}. Review in ops dashboard.
```

### 4.3 `notification_subscription`

| Column                   | Description                                                               |
| ------------------------ | ------------------------------------------------------------------------- |
| `subscription_id`        | Unique ID                                                                 |
| `event_code`             | FK → `event_def` — e.g. `SLA_BREACH`, `APPROVAL_SUBMITTED`                |
| `channel_type`           | `EMAIL`, `SMS`, …                                                         |
| `template_code`          | FK → `notification_template`                                              |
| `provider_id`            | FK → `notification_provider_def` (nullable → auto-resolve by scope)       |
| `recipient_type`         | `ROLE`, `USER_LIST`, `EXPRESSION`, `APPROVAL_ASSIGNEE`, `PARTNER_CONTACT` |
| `recipient_ref`          | Keycloak role, JSON email list, JSONata path, or contact type             |
| `filter_expression`      | JSONata on event payload                                                  |
| `scope_type`, `scope_id` | Country/partner filter                                                    |
| `enabled`                | Boolean                                                                   |

### 4.4 `notification_delivery_log`

| Column              | Description                                        |
| ------------------- | -------------------------------------------------- |
| `delivery_id`       | UUID                                               |
| `subscription_id`   | Nullable for ad-hoc sends                          |
| `event_code`        | Triggering event                                   |
| `channel_type`      | EMAIL / SMS / …                                    |
| `provider_code`     | Which vendor delivered                             |
| `recipient`         | Masked in UI (e.g. `j***@bank.com`, `+234***4567`) |
| `status`            | `SENT`, `FAILED`, `THROTTLED`, `BOUNCED`           |
| `vendor_message_id` | External ID for support tickets                    |
| `error_detail`      | Failure reason                                     |
| `sent_at`           | Timestamp                                          |
| `correlation_id`    | Platform correlation                               |

***

## 5. End-to-End Flow

```mermaid
sequenceDiagram
  participant Source as Event / Workflow / API
  participant Bus as Event Bus
  participant ND as h2h-notification-dispatcher
  participant DB as Config Store
  participant Vault
  participant Prov as NotificationProvider
  participant Vendor as Twilio / SES / etc.

  Source->>Bus: SLA_BREACH (event_code)
  Bus->>ND: EventNotificationListener
  ND->>DB: Resolve notification_subscription
  ND->>DB: Load notification_template
  ND->>ND: RecipientResolver (ROLE / EXPRESSION)
  ND->>DB: Resolve notification_provider_def
  ND->>Vault: credential_ref
  ND->>Prov: send(NotificationRequest)
  Prov->>Vendor: HTTPS API
  Vendor-->>Prov: messageId
  Prov-->>ND: DeliveryResult
  ND->>DB: notification_delivery_log
```

### 5.1 Direct send (workflow / admin)

Camunda approval tasks and admin "test notification" call `NotificationService` directly — no event round-trip:

```java
notificationService.send(SendNotificationCommand.builder()
    .channel(NotificationChannel.EMAIL)
    .templateCode("APPROVAL_PENDING")
    .recipient("supervisor@bank.com")
    .variables(Map.of("entityType", "integration_profile", "partnerName", "ACME"))
    .correlationId(context.correlationId())
    .build());
```

***

## 6. Use Cases

| Scenario              | Trigger                  | Channel        | Recipient                                       |
| --------------------- | ------------------------ | -------------- | ----------------------------------------------- |
| Approval pending      | `APPROVAL_SUBMITTED`     | EMAIL + SMS    | `APPROVAL_ASSIGNEE`                             |
| SLA breach            | `SLA_BREACH`             | EMAIL, SMS     | `ROLE:OPS_NOC`                                  |
| Payment failed        | `PAYMENT_FAILED`         | EMAIL          | `ROLE:INTEGRATION_SUPERVISOR` + partner contact |
| Cert expiry (30d)     | `SECURITY_EVENT`         | EMAIL          | `ROLE:SECURITY_OFFICER`                         |
| Job failure           | `JOB_FAILED`             | SLACK          | `#h2h-alerts` webhook                           |
| Partner ACK ready     | `FILE_DELIVERED`         | SMS (optional) | `PARTNER_CONTACT` — `contact.mobile`            |
| Sandbox test complete | `SANDBOX_TEST_COMPLETED` | EMAIL          | Submitting analyst                              |
| DR failover           | `DR_FAILOVER`            | SMS + EMAIL    | On-call roster                                  |

Partner ERP integration remains **webhooks** ([14 Extensibility](/docs/14-extensibility-framework.md) §9.2). Email/SMS to partner **contacts** uses this notification layer.

***

## 7. Provider Implementations

### 7.1 Email

| Provider JAR                | Vendor     | Typical deployment          |
| --------------------------- | ---------- | --------------------------- |
| `h2h-notification-aws-ses`  | Amazon SES | AWS regions                 |
| `h2h-notification-sendgrid` | SendGrid   | Cloud-agnostic              |
| `h2h-notification-smtp`     | SMTP relay | On-prem / bank mail gateway |

**Controls:** SPF/DKIM/DMARC configured at infrastructure; `from_address` in `connection_params`; HTML templates sanitized (no script tags).

### 7.2 SMS (Africa multi-country)

| Provider JAR                       | Markets         | Notes                         |
| ---------------------------------- | --------------- | ----------------------------- |
| `h2h-notification-africas-talking` | Pan-Africa      | Primary for NG, GH, KE, UG, … |
| `h2h-notification-termii`          | Nigeria         | Local sender ID compliance    |
| `h2h-notification-twilio`          | Global fallback | Higher cost, broad coverage   |

**Country pack:** Each country row in `notification_provider_def` with `scope_type=COUNTRY`. Runtime picks highest-priority enabled provider for message origin country.

**Compliance:** SMS content templates pre-approved where regulators require (e.g. sender ID registration). Templates go through maker–checker publish.

### 7.3 Slack / Teams (internal ops)

| Provider JAR             | Channel            |
| ------------------------ | ------------------ |
| `h2h-notification-slack` | `SLACK`            |
| `h2h-notification-teams` | `TEAMS` (optional) |

Uses incoming webhook URL in Vault; suitable for NOC alert channels, not partner-facing.

### 7.4 Push (optional)

`h2h-notification-fcm` for partner mobile apps — low priority; most partners use WebSocket or webhook.

***

## 8. Reliability & Security

| Concern                        | Approach                                                                     |
| ------------------------------ | ---------------------------------------------------------------------------- |
| **Retry**                      | Exponential backoff; max 5 attempts; dead-letter to `NOTIFICATION_DLQ` event |
| **Idempotency**                | `delivery_idempotency_key` = hash(event\_id + subscription\_id + recipient)  |
| **Rate limits**                | Per-provider `rate_limit_per_min`; per-country SMS caps                      |
| **PII**                        | Mask phone/email in logs; full value only in Vault audit                     |
| **Secrets**                    | API keys **only** in Vault via `credential_ref`                              |
| **URL / recipient validation** | Email domain allowlist for internal; E.164 normalize for SMS                 |
| **Opt-out**                    | `notification_opt_out` table per recipient + channel (regulatory)            |
| **Failover**                   | Try priority 2 provider on 5xx / timeout from priority 1                     |

***

## 9. Admin API

| Method   | Path                                 | Description                      |
| -------- | ------------------------------------ | -------------------------------- |
| GET/POST | `/notifications/providers`           | CRUD `notification_provider_def` |
| GET/POST | `/notifications/templates`           | CRUD templates (draft → publish) |
| GET/POST | `/notifications/subscriptions`       | CRUD event → channel bindings    |
| POST     | `/notifications/templates/{id}/test` | Send test to specified address   |
| GET      | `/notifications/deliveries`          | Delivery log search              |
| POST     | `/notifications/send`                | Ad-hoc send (supervisor only)    |

Publish workflow for templates and production provider changes uses [40 Workflow Architecture](/docs/40-workflow-architecture.md) maker–checker.

***

## 10. Events

| Event code            | Purpose                            |
| --------------------- | ---------------------------------- |
| `NOTIFICATION_SENT`   | Successful delivery (audit stream) |
| `NOTIFICATION_FAILED` | Exhausted retries                  |
| `SLA_BREACH`          | Triggers ops alert subscriptions   |
| `APPROVAL_SUBMITTED`  | Triggers checker notification      |

Physical destinations from `event_channel_def` ([46 Database-Driven Events](/docs/46-database-driven-events.md)).

**Fix observability doc:** SLA alerts publish `SLA_BREACH` — not a hardcoded `h2h.alerts` topic.

***

## 11. Camel Integration (optional step)

For synchronous notify inside a route (rare — prefer async events):

```java
.to("notification:send?channel=SMS&templateCode=FILE_RECEIVED_ALERT")
```

`notification:` Camel component delegates to `NotificationService`. **Default:** publish event and let dispatcher handle async.

***

## 12. Creating a New Provider (Checklist)

1. Create `h2h-notification-{vendor}/` module implementing `NotificationProvider`.
2. Register via Spring Boot `AutoConfiguration` + `META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports`.
3. Add vendor enum to admin UI provider picker.
4. Insert seed row in `notification_provider_def` with `credential_ref` placeholder.
5. Store API credentials in Vault; link `credential_ref`.
6. Add contract tests with vendor sandbox / WireMock.
7. Document rate limits and error code mapping in module README.

**No changes** to `h2h-notification-dispatcher` or routes — only new JAR on classpath + DB row.

***

## 13. Related Documents

* [14 Extensibility Framework](/docs/14-extensibility-framework.md) — webhooks vs notifications
* [17 Monitoring and Logging](/docs/17-monitoring-and-logging.md) — SLA breach → `SLA_BREACH`
* [21 Event-Driven Runtime Extensibility](/docs/21-event-driven-runtime-extensibility.md) — event subscriptions
* [40 Workflow Architecture](/docs/40-workflow-architecture.md) — approval task notifications
* [46 Database-Driven Events](/docs/46-database-driven-events.md) — event catalog
* [47 Gateway Entry Points](/docs/47-gateway-entrypoints.md) — WebSocket for partner real-time


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://host2host.onibonje.com/docs/48-utility-notification-integrations.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
