> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aspfox.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Production Checklist

> Required checks before going live. Security, Stripe, email, database, OAuth, and frontend.

Work through every item before announcing your application publicly.

## Security

<Steps>
  <Step title="Generate new JWT RSA key pair">
    Do not use the keys from local development. Generate a fresh pair specifically for production.

    ```bash theme={null}
    openssl genrsa -out private.pem 4096
    openssl rsa -in private.pem -pubout -out public.pem
    base64 -w 0 private.pem    # copy to JWT_PRIVATE_KEY
    base64 -w 0 public.pem     # copy to JWT_PUBLIC_KEY
    rm private.pem public.pem
    ```
  </Step>

  <Step title="Change default admin credentials">
    Log in to the production admin account and change the password from `Admin123!` to something secure. Do not skip this — the default password is the same for every scaffolded project.
  </Step>

  <Step title="Verify ENVIRONMENT=Production">
    In production, this setting: disables Swagger (do not expose Swagger publicly), suppresses detailed error messages in API responses, causes missing Stripe configuration to throw on startup rather than warn.

    ```bash theme={null}
    ASPNETCORE_ENVIRONMENT=Production
    ```
  </Step>

  <Step title="Confirm Swagger is not accessible">
    Navigate to `https://api.yourdomain.com/swagger` — it should return 404, not the Swagger UI.
  </Step>

  <Step title="Verify HTTPS is configured">
    * HTTP requests should redirect to HTTPS
    * SSL certificate is valid (not self-signed in production)
    * HSTS header is present: `Strict-Transport-Security: max-age=31536000`
  </Step>

  <Step title="Verify no secrets are in code or config files">
    Run this from the project root before deploying:

    ```bash theme={null}
    git log --all --full-history -- '*.env'
    git grep -i "sk_live_"
    git grep -i "whsec_"
    ```

    Any results indicate secrets are committed to git. Rotate those keys immediately.
  </Step>
</Steps>

## Stripe

<Steps>
  <Step title="Switch to live Stripe keys">
    `STRIPE_SECRET_KEY` must start with `sk_live_`, not `sk_test_`. Check in your environment variables.
  </Step>

  <Step title="Webhook endpoint is registered in Stripe Dashboard">
    Dashboard → Developers → Webhooks. Verify an endpoint exists for your production API URL.
  </Step>

  <Step title="All five required events are selected">
    The endpoint must be subscribed to exactly these events:

    * `checkout.session.completed`
    * `customer.subscription.updated`
    * `customer.subscription.deleted`
    * `invoice.payment_failed`
    * `invoice.payment_succeeded`
  </Step>

  <Step title="Webhook signing secret is the live endpoint secret">
    `STRIPE_WEBHOOK_SECRET` must start with `whsec_` and come from the live endpoint, not from the Stripe CLI. The CLI secret and the Dashboard endpoint secret are different values.
  </Step>

  <Step title="Complete a real test checkout">
    Use a real card (not a test card) to complete a Pro checkout. Verify the subscription appears in the Stripe Dashboard and the Billing page in your application shows the updated plan.
  </Step>
</Steps>

## Email

<Steps>
  <Step title="Sending domain is verified in Resend">
    Resend Dashboard → Domains. Status must be **Verified**, not Pending.
  </Step>

  <Step title="EMAIL_FROM_ADDRESS uses your verified domain">
    Must be `noreply@yourdomain.com` or similar. Not `onboarding@resend.dev`.
  </Step>

  <Step title="Send a test email and check deliverability">
    Register a new account from the public-facing sign-up page. The verification email should arrive within 30 seconds. Check the spam folder — if it lands there, check your SPF/DKIM/DMARC records.
  </Step>
</Steps>

## Database

<Steps>
  <Step title="Migrations applied to production database">
    ```bash theme={null}
    dotnet ef database update \
      --project src/Acme.Infrastructure \
      --startup-project src/Acme.Api
    ```

    The command should print `No pending migrations.` when complete.
  </Step>

  <Step title="Seed data applied">
    The admin user must exist. Run `make seed` or the equivalent command against the production database once.
  </Step>

  <Step title="Database backups are configured">
    If using a managed database (Railway, Render, RDS), enable automatic backups in the provider dashboard. If self-hosting PostgreSQL, configure `pg_dump` via cron.
  </Step>
</Steps>

## OAuth

<Steps>
  <Step title="Production redirect URIs configured in Google Cloud Console">
    The authorized redirect URI must be `https://api.yourdomain.com/api/v1/auth/google/callback`. Not `localhost`.
  </Step>

  <Step title="Production callback URL configured in GitHub OAuth App">
    The authorization callback URL must be `https://api.yourdomain.com/api/v1/auth/github/callback`. Not `localhost`.
  </Step>
</Steps>

## Frontend

<Steps>
  <Step title="VITE_API_URL points to production API URL">
    Must be `https://api.yourdomain.com`, not `http://localhost:5000`.
  </Step>

  <Step title="Frontend build uses production environment variables">
    If using Vite's build command directly: `VITE_API_URL=https://api.yourdomain.com npm run build`. Verify the built JavaScript files do not contain `localhost`.

    ```bash theme={null}
    grep -r "localhost" dist/    # should return no results
    ```
  </Step>
</Steps>

## Notifications and Redis

<Steps>
  <Step title="Redis is running and accessible">
    The API must be able to connect to Redis. Verify:

    ```bash theme={null}
    curl https://api.yourdomain.com/api/v1/notifications/unread-count \
      -H "Authorization: Bearer <admin-token>"
    # Should return {"success":true,"data":{"count":0}}
    # A Redis connection failure causes this to return 500
    ```
  </Step>
</Steps>
