# RC1 Deployment Guide

Public API semantics remain governed by [API_V1_CONTRACT.md](api/API_V1_CONTRACT.md) and the authoritative [OpenAPI 3.1 document](api/openapi.yaml). Deployment must not reinterpret either contract.

## Scope

This guide deploys the implemented RC1 application. It does not define infrastructure automation, a process supervisor, a persistent SMPP runtime command, webhook retries, or a production rollback policy on behalf of an operator. Those require an approved environment-specific change plan.

## Repository requirements

- PHP: `^8.2` from `composer.json`; the verified development stack uses PHP 8.4.
- Framework: Laravel `^12.0`.
- Database: MySQL with InnoDB for the dedicated RC persistence and constraint verification.
- Composer production platform requirements: `ctype`, `dom`, `fileinfo`, `filter`, `hash`, `iconv`, `json`, `libxml`, `mbstring`, `openssl`, `pcre`, `session`, `sockets`, and `tokenizer` (native or Composer-provided polyfills where reported by `composer check-platform-reqs --no-dev`).
- MySQL access also requires the applicable PDO/MySQL driver.
- The web server must serve Laravel's `public` directory and support HTTPS.

Verify the target rather than assuming parity:

```console
php --version
composer check-platform-reqs --no-dev
php artisan about
```

## Environment preparation

Create the deployment environment using the repository's `.env.example` as the key inventory, then provide production values through approved secret management. At minimum review:

- `APP_ENV=production`
- `APP_DEBUG=false`
- `APP_KEY` generated once and retained securely
- `APP_URL`
- `DB_CONNECTION=mysql`, `DB_HOST`, `DB_PORT`, `DB_DATABASE`, `DB_USERNAME`, `DB_PASSWORD`
- `CACHE_STORE`, `QUEUE_CONNECTION`, and Redis keys when those configured stores are used
- `LOG_CHANNEL`, `LOG_STACK`, and an appropriate `LOG_LEVEL`
- the exact YAS keys listed in [YAS_INTEGRATION_GUIDE.md](YAS_INTEGRATION_GUIDE.md)

Do not regenerate `APP_KEY` for an existing environment: it protects encrypted persisted values, including webhook secrets and SMPP inbound content. Never commit `.env`, API credentials, SMPP passwords, or webhook secrets.

Ensure the application process can write Laravel's `storage` and `bootstrap/cache` directories. Use environment-owned service accounts and least-privilege database credentials. Public API and webhook URLs require HTTPS. The repository does not implement a YAS SMPP TLS environment switch; confirm transport requirements with YAS before approval.

## Pre-deployment and rollback preparation

Before changing production:

1. Identify the exact approved Git commit and release identifier.
2. Take and verify a recoverable database backup using the organization's approved MySQL procedure.
3. Record current application and migration state with `php artisan about` and `php artisan migrate:status`.
4. Dry-run all migrations against a production-shaped disposable database.
5. Confirm old application instances are compatible with the additive RC schema during the deployment window.
6. Prepare an application rollback artifact and a database recovery decision before migration.
7. Assign deployment, database, SMPP, and incident owners.

Do not use `migrate:fresh`, `db:wipe`, or an unreviewed destructive rollback against production.

## Deployment sequence

Run from the approved release directory:

```console
composer install --no-dev --optimize-autoloader
php artisan down
php artisan migrate --force
php artisan optimize
php artisan up
```

`php artisan down` affects application availability and must follow the approved change window. If the deployment strategy uses parallel immutable releases, use the platform's approved traffic-switch procedure instead; no such procedure is implemented in this repository.

After deployment, confirm:

```console
php artisan about
php artisan migrate:status
php artisan route:list --path=api/v1
php artisan db:show
```

Do not run `php artisan key:generate` on an existing production environment. It is appropriate only when establishing a new environment whose encrypted data does not yet exist.

## RC database changes

Deploy these migrations in repository order:

| Milestone | Migration | Purpose |
| --- | --- | --- |
| RC1-2 | `2026_07_19_000200_harden_message_submission_idempotency.php` | Tenant-scoped durable request replay and message correlation/client-reference fields |
| RC1-3 | `2026_07_19_000300_create_bulk_message_submission_idempotencies_table.php` | Durable ordered bulk replay metadata |
| RC1-5 | `2026_07_19_000400_create_message_delivery_receipts_table.php` | Immutable normalized DLR evidence and correlation indexes/constraints |
| RC1-6 | `2026_07_19_000500_create_tenant_delivery_webhooks.php` | One tenant configuration, terminal events, and one delivery attempt |

Run all pending migrations with `php artisan migrate --force`; do not execute individual migration files manually. The migrations are designed for the committed application sequence. Validate InnoDB tables, indexes, foreign keys, and check constraints in the staging dry run.

Safe read-only verification includes:

```console
php artisan migrate:status
php artisan db:table message_submission_idempotencies
php artisan db:table bulk_message_submission_idempotencies
php artisan db:table message_delivery_receipts
php artisan db:table tenant_webhook_configurations
php artisan db:table tenant_webhook_events
php artisan db:table tenant_webhook_delivery_attempts
```

Rollback can remove tables, columns, constraints, replay history, DLR evidence, webhook configuration, and delivery-attempt evidence. Production rollback must therefore be decided from the approved backup/change procedure, not by automatically invoking `migrate:rollback`. If code rollback is compatible with the additive schema, prefer retaining data until the database owner approves a schema reversal.

## Existing SMPP commands

The repository provides these explicit operational/diagnostic commands:

```console
php artisan gateway:smpp:bind-test yas
php artisan gateway:sms:send-test yas --to=<TEST_MSISDN> --from=<APPROVED_SENDER> --message=<APPROVED_TEST_TEXT> --request-dlr
php artisan gateway:publish-outbox --dry-run --limit=100
php artisan gateway:publish-outbox --once
```

The live SMS command requires operator confirmation unless `--force` is supplied. Do not use `--force` casually. The outbox command processes existing outbound events; it is not a persistent SMPP runtime supervisor.

There is no implemented Artisan command that starts, supervises, or restarts a persistent `RuntimeLoop`. Do not invent one. If production operation requires a long-lived SMPP process, that remains a separately approved deployment capability and a release blocker until supplied.

## Smoke checks

1. Confirm application boot, routes, and database connectivity with the commands above.
2. Confirm invalid authentication is rejected without disclosing tenant data.
3. Use a dedicated staging tenant to submit one message and capture `message_id` and `X-Correlation-ID`.
4. Confirm `GET /api/v1/messages/{message_id}` returns only that tenant's resource.
5. Run the YAS bind test only inside the approved external test window.
6. Send a live test only with an approved MSISDN, sender, text, and YAS authorization.
7. Verify DLR projection and webhook delivery using [ACCEPTANCE_CHECKLIST.md](ACCEPTANCE_CHECKLIST.md).

## Failed deployment

Keep the application in maintenance mode if migrations, boot, route, database, tenant-isolation, or secret checks fail. Preserve logs and sanitized evidence, stop external tests, and invoke the approved rollback or recovery plan. Never paste credentials, message bodies, full recipient numbers, or secrets into incident tickets.
