# Messaging Service manual

## Contract

`messaging_service` is a Platform App Standard v1 app. It has no dependency on
QR, login, invoices, fleet systems, WordPress, or AroFlo. Tenant identity and
the requesting principal come from the authenticated gateway context. All
responses use the platform 1.0 envelope.

Delivery is synchronous in v1 because the current `QueueManager` does not run
workers. A delivery and each attempt are persisted before returning, so a
future worker can be added without changing consumer contracts. OTP generation,
validation, authentication and session creation deliberately remain outside
this service.

## Exported capabilities

- `platform.health` / `health_v1`: local health only; no network request.
- `messaging.template.send` / `send_template_v1`: authorized template delivery.
- `messaging.delivery.status.read` / `get_delivery_status_v1`: caller-scoped status.
- `messaging.provider.health.check` / `health_check_provider_v1`: admin-only provider probe.
- `messaging.provider.configure` / `configure_provider_v1`: admin-only write-only provider setup.
- `messaging.permission.configure` / `configure_permission_v1`: admin-only caller allowlist.

Example internal call (the caller manifest must allow the capability):

```python
from bridge_platform.interapp.client import call_capability

result = call_capability(context, "messaging_service", "messaging.template.send", {
    "channel": "sms",
    "recipient": {"phone": "+61404493988", "email": None, "user_id": "user-42"},
    "template_key": "security_login_code_sms",
    "template_data": {"code": "428193", "expiry_minutes": 5},
    "purpose": "qr_login",
    "idempotency_key": "qr-login-attempt-8af31d",
    "metadata": {"correlation_id": "login-8af31d"},
})
```

WordPress uses Bridge Core, never the Android address or credentials:

```php
$result = wp_flask_bridge_invoke('messaging_service', 'send_template_v1', array(
    'channel' => 'sms',
    'recipient' => array('phone' => '+61404493988', 'email' => null, 'user_id' => '42'),
    'template_key' => 'security_login_code_sms',
    'template_data' => array('code' => '428193', 'expiry_minutes' => 5),
    'purpose' => 'qr_login',
    'idempotency_key' => 'qr-login-attempt-8af31d',
));
```

## Provider configuration

Provider configuration is tenant-scoped. Secrets are stored under
`app_id=messaging_service` in the platform vault. `configure_provider_v1`
accepts a new `secret` value but returns only `secret_configured`; reads never
return the value. Administrative UI can bind directly to these admin actions
without creating another console.

Android fields: `name`, `driver=android_local`, `channel=sms`, `config.base_url`,
`config.endpoint`, `config.connect_timeout`, `config.read_timeout`, priority,
limits, retry count, enabled state, and the write-only token. The v1 driver
allows only origin `http://10.8.0.2:8082`, rejects URL credentials, query
strings, fragments, path traversal and redirects, and sends exactly
`Authorization: <token>` (not Bearer).

SMTP fields: `driver=smtp`, `channel=email`, `config.host`, `config.port`,
`config.encryption` (`none`, `starttls`, or `ssl`), `config.username`,
`config.from_name`, `config.from_email`, optional `config.reply_to`, timeout,
and the write-only password. Normal health checks connect, negotiate TLS,
optionally authenticate, and issue `NOOP`; they do not send mail.

## Templates

Built-in version-1 templates cover login codes, password reset, and system
alerts for SMS/email. Database templates can override them per tenant. The
renderer only substitutes declared `{{ variable }}` placeholders, rejects
missing and unexpected variables, never evaluates code, and HTML-escapes
values in HTML bodies. Template key/version are stored on each delivery;
rendered security content is not stored or logged.

## Security and tenant isolation

- Providers, templates, permissions, deliveries and attempts are tenant-aware.
- The tenant and caller are derived from verified gateway identity, not payload.
- Every caller needs an explicit channel/template/purpose allowlist.
- Recipient addresses are normalized, stored for delivery, and masked in logs.
- Security flows cannot submit arbitrary body text.
- Idempotency is unique over tenant, caller, operation and key; concurrent
  duplicates resolve to the existing delivery.
- Recipient and purpose burst limits run before provider contact; platform and
  provider limits add further layers.
- Network timeouts are bounded. Android redirects and non-allowlisted origins
  are rejected. Provider exceptions and response bodies are never exposed.

For a new cloud SMS vendor, implement the driver contract
`validate_configuration`, `health_check`, and `send`, register it once in
`drivers.default_registry`, and configure providers. Consumers do not change.
Migration from Android to a cloud provider is therefore configuration: add the
new higher/lower-priority provider and explicitly enable fallback for approved
caller policy when required.

## Idempotency, retries, limits and fallback

One unique delivery exists for each tenant/caller/operation/idempotency key.
Transient network, HTTP 5xx, and SMTP 4xx failures can retry up to the provider
maximum. Credentials, invalid recipient/configuration and terminal responses
do not retry. Fallback stays disabled per caller unless an administrator
explicitly enables it; cross-channel fallback is not implemented.

## Health checks

Android performs a bounded GET and treats controlled 2xx/3xx responses as
reachable without following a redirect. SMTP validates the connection/TLS and
optional authentication without sending. Status, latency, last check, safe
error category and consecutive failure count are persisted.

## Error contract

Stable errors include `invalid_input`, `invalid_recipient`,
`permission_denied`, `template_not_found`, `template_invalid`,
`provider_unavailable`, `provider_not_found`, `rate_limit_exceeded`,
`delivery_not_found`, `tenant_context_missing`, `caller_context_missing`, and
`invalid_configuration`. Envelopes contain safe messages and retryability, not
raw exceptions or provider bodies.

## Observability

Structured events contain tenant, caller, delivery/provider IDs, masked
recipient, channel, template, purpose, status and correlation ID. Audit events
record the action and safe outcome. OTP values, message bodies, tokens,
passwords and Authorization headers are excluded.

## Testing and validation

Automated checks cover manifest validity, strict rendering, normalization,
driver selection, Android request/header/redirect/SSRF behavior, SMTP TLS and
credential handling, and WordPress helper boundaries. They use mocks or the
fake provider and never send a real message.

Optional real test, only after automatic checks pass:

1. Apply the control-plane migration and register/enable `messaging_service`.
2. Invoke `configure_provider_v1` as an admin with base URL
   `http://10.8.0.2:8082`, endpoint `/`, and the real token in `secret`.
3. Invoke `configure_permission_v1` for the trusted caller (for WordPress use
   `wordpress_bridge`) allowing `sms`, `security_login_code_sms`, and a test
   purpose such as `manual_gateway_test`.
4. Invoke `send_template_v1` through the gateway or App Pack with recipient
   `+61404493988`, a fresh idempotency key, and non-production test data.
5. Record the returned `delivery_id`; inspect masked audit/delivery status.
   Reusing the key must return the same ID and must not send again.

No token belongs in shell history, documentation, fixtures, migrations, or
versioned configuration. Prefer the administrative console's secret field.

## Versioning and compatibility

App version is `1.0.0`; actions are versioned `_v1` and envelopes use contract
1.0. New providers are additive. Breaking request/output changes require new
actions or a contract major version. The WordPress pack is optional and uses
only the public Bridge API. Rollback disables the tenant app/providers first;
the migration downgrade removes messaging data and should only run after an
approved backup.
