This document covers order statuses, email flows, and production deployment for the NSVA Ship's Store.
Order Statuses
| Status | Counts toward notifications badge? | Emails sent |
|---|---|---|
| PENDING | No | — |
| PAID | Yes | Order confirmation (on payment) |
| PROCESSING | Yes | — |
| CONFIRMED | No | Order confirmation |
| SHIPPED | No | Order shipped (with tracking) |
| DELIVERED | No | — |
| CANCELLED | No | — |
| REFUNDED | No | — |
PENDING (unpaid) and CONFIRMED do not count toward the notifications badge. Only PAID and PROCESSING orders need fulfillment action.
Email Flows
1. Order placed (before payment)
- No emails sent — avoids emailing when payment is abandoned/fails
- Trigger:
POST /api/store/orders
2. Payment confirmed
- Customer: "Order confirmed"
- Admin: "New order" (paid)
- Trigger: Stripe webhook
checkout.session.completed→confirmOrderPayment()
3. Admin sets status to CONFIRMED
- Customer: "Order confirmed"
- Trigger:
PATCH /api/admin/orders/[id]withstatus: "CONFIRMED"
4. Admin sets status to SHIPPED
- Customer: "Order shipped" (includes tracking number if set)
- Trigger:
PATCH /api/admin/orders/[id]withstatus: "SHIPPED"
Environment Variables
Email (Microsoft 365 — production)
| Variable | Required | Description |
|---|---|---|
EMAIL_PROVIDER |
Yes | smtp for production (M365) |
SMTP_HOST |
Yes | smtp.office365.com |
SMTP_PORT |
Yes | 587 (with SMTP_SECURE=false) |
SMTP_SECURE |
Yes | false for STARTTLS on 587 |
SMTP_USER |
Yes | Sending mailbox, e.g. noreply@nsva.org |
SMTP_PASS |
Yes | Mailbox or app password (tenant policy) |
EMAIL_FROM |
Yes | e.g. NSVA <noreply@nsva.org> |
EMAIL_OVERRIDE_TO |
No | Never set in production (redirects outbound mail) |
Payments (Stripe)
| Variable | Required | Description |
|---|---|---|
PAYMENT_PROVIDER |
Yes | stripe |
STRIPE_SECRET_KEY |
Yes | sk_live_... for production |
STRIPE_WEBHOOK_SECRET |
Yes | whsec_... from Stripe webhook |
Production Deployment Checklist
1. Microsoft 365 (email)
- Create or designate
noreply@nsva.org(or approved sender) in the NSVA tenant - Enable SMTP AUTH on that mailbox if required by policy
- Store
SMTP_USER/SMTP_PASSin Secret Manager (nsva-smtp-user,nsva-smtp-pass) - Store
EMAIL_FROMinnsva-email-from(e.g.NSVA <noreply@nsva.org>) - Do not set
EMAIL_OVERRIDE_TOin production
If emails still don’t send in production:
- Cloud Run logs — Look for
[EmailService] SMTP failed:and the error message (authentication, relay, etc.). - Credentials — Confirm
SMTP_USER/SMTP_PASSand that the mailbox allows SMTP submission. - Test endpoint —
GET https://your-run-url.run.app/api/test-email?to=recipient@example.com(only whenALLOW_TEST_EMAIL=truein production). Failed sends returndeliveryErrorin the JSON body.
2. Stripe (Payments)
- Switch to live account in Stripe Dashboard
- Complete business verification
- Create secret
nsva-stripe-secret-keywith live secret key (sk_live_...) - Create webhook in Stripe Dashboard:
- URL:
https://nsva-frontend-nxgr3bdqqq-uc.a.run.app/api/webhooks/stripe - Event:
checkout.session.completed
- URL:
- Create secret
nsva-stripe-webhook-secretwith webhook signing secret (whsec_...)
3. GCP Secret Manager
Required secrets for production:
| Secret name | Contents |
|---|---|
nsva-database-url |
PostgreSQL connection string |
nsva-jwt-secret |
JWT signing secret |
nsva-jwt-refresh-secret |
Refresh token secret |
nsva-encryption-key |
32-char encryption key |
nsva-smtp-user |
M365 mailbox user (e.g. noreply@nsva.org) |
nsva-smtp-pass |
M365 password or app password |
nsva-email-from |
Sender display, e.g. NSVA <noreply@nsva.org> |
nsva-stripe-secret-key |
Stripe live secret key |
nsva-stripe-webhook-secret |
Stripe webhook signing secret |
nsva-pii-purge-secret |
For PII purge cron (optional) |
nsva-cron-secret |
NSVA_CRON_SECRET — auth for /api/cron/membership-renewal-reminders and /api/cron/commander-digest |
4. Cloud Build
cloudbuild.yaml sets EMAIL_PROVIDER=smtp, Microsoft 365 SMTP host/port, and wires SMTP + EMAIL_FROM secrets to Cloud Run. Push to main to trigger deployment.
5. NSVA cron jobs (membership renewal & Commander digest)
These are not part of the Ship’s Store; they apply to the whole site. Configure Google Cloud Scheduler to POST the cron routes with NSVA_CRON_SECRET.
- Full
gcloudexamples and env vars:ADMIN-SITE-OVERVIEW.md— section 9) GCP Cloud Scheduler (cron jobs).
6. Object storage (GCS) — uploads, images, DD-214, archives
When STORAGE_PROVIDER=gcs (set in cloudbuild.yaml), the app needs a bucket and IAM on the Cloud Run service account.
Create a bucket (name must match
GCS_BUCKET; default incloudbuild.yamlisnsva-489001-filesvia_GCS_BUCKET). GCS names are global—if taken, pick another name and set_GCS_BUCKETon the trigger.gsutil mb -p nsva-489001 -l US-CENTRAL1 gs://nsva-489001-filesGrant the Cloud Run runtime identity access to objects in that bucket (typically the default compute service account
PROJECT_NUMBER-compute@developer.gserviceaccount.com, or the custom SA if you attach one):- Role: Storage Object Admin (
roles/storage.objectAdmin) on that bucket, or a tighter custom role if you prefer.
- Role: Storage Object Admin (
Deploy a build that includes
@google-cloud/storageandGCS_BUCKETin the service env (already wired incloudbuild.yaml).Manual CLI deploys: pass the same vars, e.g.
gcloud run deploy ... --set-env-vars=STORAGE_PROVIDER=gcs,GCS_BUCKET=nsva-489001-files,....
Public pages load allowed prefixes through GET /api/files/object/.... Private object keys (for example under dd214/ and archive/) are not exposed through public URLs.
Order lifecycle, Stripe checkout, webhooks, notifications, and admin order tools run in the deployed Cloud Run application using the environment variables and secrets above.