# Notification Provider Setup

## Email

Configure Laravel `MAIL_*` variables. Local development defaults to the `log` mailer. Production can use SMTP, SES, Postmark or another Laravel-supported transport. Appointment email reminders, password reset and staff email verification all require a working mail transport.

## WhatsApp Cloud API

```dotenv
WHATSAPP_ENABLED=true
WHATSAPP_GRAPH_BASE_URL=https://graph.facebook.com
WHATSAPP_GRAPH_VERSION=v23.0
WHATSAPP_PHONE_NUMBER_ID=...
WHATSAPP_ACCESS_TOKEN=...
WHATSAPP_TEMPLATE_APPOINTMENT_REMINDER=appointment_reminder
WHATSAPP_TEMPLATE_LANGUAGE=en
```

The adapter sends an approved template through Meta's Graph API. WhatsApp reminders are only attempted when the patient has `has_whatsapp=true`, a valid WhatsApp number, channel preference enabled and recorded consent (`consent_at`).

## Expo push

```dotenv
EXPO_PUSH_ENABLED=true
EXPO_PUSH_URL=https://exp.host/--/api/v2/push/send
```

Register devices with `POST /api/v1/devices` using provider `expo`.

## Firebase Cloud Messaging (HTTP v1)

```dotenv
FCM_PUSH_ENABLED=true
FCM_PROJECT_ID=your-project-id
FCM_SERVICE_ACCOUNT_PATH=/secure/path/firebase-service-account.json
# Or provide FCM_SERVICE_ACCOUNT_JSON via the secret manager.
```

Register the device with provider `fcm`. The backend signs a short-lived OAuth service-account assertion and sends through the FCM HTTP v1 endpoint. Never ship the service account to the mobile bundle.

## Apple Push Notification service

```dotenv
APNS_PUSH_ENABLED=true
APNS_ENVIRONMENT=production
APNS_KEY_ID=...
APNS_TEAM_ID=...
APNS_BUNDLE_ID=com.example.smartclinic
APNS_PRIVATE_KEY_PATH=/secure/path/AuthKey_XXXX.p8
```

Register the device with provider `apns`. Token-based APNs authentication is generated server-side. Invalid/unregistered tokens are deactivated when providers report them.

## Reminder scheduling, locking and idempotency

`clinic:dispatch-reminders` scans due appointments using organization reminder offsets. The idempotency key contains appointment ID, the authoritative scheduled-start version, reminder offset and channel. This means repeated scheduler runs do not create duplicate logs, while a rescheduled appointment receives a new reminder generation. Cancellation/rescheduling supersedes pending/failed/processing reminders.

`SendAppointmentReminder` is queued with retries/backoff. Delivery also acquires a cache/Redis lock per idempotency key so concurrent jobs cannot send the same log simultaneously. Stale `processing` state is recovered by the reminder command. Keep queue workers running continuously and invoke Laravel's scheduler every minute.
