Files
JQC_multi_tenant/GOING_LIVE.md
T

9.6 KiB

Going Live — JQC Billing & Email Checklist

Steps to complete before accepting real paying customers.
Follow sections in order: Stripe first, then email, then cron, then verify.


1. Switch Stripe from Test → Live Mode

1.1 Get your live API keys

  1. Log in to dashboard.stripe.com
  2. Click the toggle in the top-left from Test modeLive mode
  3. Go to Developers → API keys
  4. Copy:
    • Secret key — starts with sk_live_…
    • Publishable key — starts with pk_live_…

1.2 Create live Products and Price IDs

You must do this in live mode — test-mode prices are not valid for real payments.

  1. Go to Products → + Add product
  2. Create three products: Starter, Pro, Enterprise
  3. For each, add a recurring price (monthly)
  4. Copy each Price ID — starts with price_live_… (NOT prod_…)

1.3 Update /etc/jqc/app.env

sudo nano /etc/jqc/app.env

Replace the test values:

STRIPE_SECRET_KEY=sk_live_XXXXXXXXXXXXXXXXXXXXXXXX
STRIPE_PUBLISHABLE_KEY=pk_live_XXXXXXXXXXXXXXXXXXXXXXXX
STRIPE_PRICE_STARTER=price_live_XXXXXXXXXXXXXXXXXXXXXXXX
STRIPE_PRICE_PRO=price_live_XXXXXXXXXXXXXXXXXXXXXXXX
STRIPE_PRICE_ENTERPRISE=price_live_XXXXXXXXXXXXXXXXXXXXXXXX

1.4 Seed the live Price IDs into the control DB

set -a; . /etc/jqc/app.env; . /etc/jqc/control.env; set +a
python -m control.cli seed
python -m control.cli list-plans   # verify all three show price_live_... IDs

1.5 Register the live Stripe webhook

  1. In Stripe Dashboard (live mode) → Developers → Webhooks → + Add endpoint
  2. Endpoint URL: https://yourdomain.jqc.app/billing/webhook (use your tenant-zero domain or the primary custom domain)
  3. Events to listen to (select these exactly):
    • invoice.payment_failed
    • customer.subscription.updated
    • customer.subscription.deleted
    • checkout.session.completed
  4. Click Add endpoint
  5. Copy the Signing secret — starts with whsec_…

1.6 Update webhook secret

sudo nano /etc/jqc/app.env
STRIPE_WEBHOOK_SECRET=whsec_XXXXXXXXXXXXXXXXXXXXXXXX

1.7 Restart the app

sudo systemctl daemon-reload
sudo systemctl restart jqc

1.8 Verify webhook delivery

  1. In Stripe Dashboard → Webhooks → click your endpoint
  2. Send a test event: invoice.payment_failed
  3. Check your app logs: tail -f /var/www/jqc/logs/jqc.log | grep BILLING
  4. You should see: BILLING WEBHOOK | event=invoice.payment_failed

2. Email Deliverability (SPF, DKIM, DMARC)

Without these DNS records, billing emails (trial ending, payment failed, etc.) will land in spam or be rejected outright.

2.1 SPF Record

SPF tells receiving servers which servers are allowed to send email for your domain.

Add a TXT record on your sending domain (e.g. jqc.app or your custom domain):

Name Type Value
@ TXT v=spf1 include:YOUR-SMTP-PROVIDER.com ~all

Replace YOUR-SMTP-PROVIDER.com with your actual SMTP provider's include directive:

Provider Include value
SendGrid include:sendgrid.net
Mailgun include:mailgun.org
Amazon SES include:amazonses.com
Google Workspace include:_spf.google.com
Your own server ip4:YOUR.SERVER.IP.ADDRESS

Example for your own server:

v=spf1 ip4:123.45.67.89 ~all

Example for SendGrid:

v=spf1 include:sendgrid.net ~all

⚠️ Only one SPF record per domain is allowed. If one already exists, append to it.

2.2 DKIM Record

DKIM cryptographically signs outgoing emails so recipients can verify they're authentic.

If using SendGrid / Mailgun / Amazon SES:

  • Follow their dashboard instructions to generate a DKIM key pair
  • They will give you TXT records to add (usually at mail._domainkey.yourdomain.com)
  • Add those records exactly as provided

If sending from your own server (Postfix + OpenDKIM):

# Install OpenDKIM
sudo apt install opendkim opendkim-tools

# Generate key pair
sudo mkdir -p /etc/opendkim/keys/yourdomain.com
sudo opendkim-genkey -b 2048 -d yourdomain.com -D /etc/opendkim/keys/yourdomain.com -s mail -v

# View the TXT record to add to DNS
sudo cat /etc/opendkim/keys/yourdomain.com/mail.txt

Add the output as a TXT record at mail._domainkey.yourdomain.com.

2.3 DMARC Record

DMARC tells receiving servers what to do with emails that fail SPF/DKIM checks, and where to send failure reports.

Add a TXT record:

Name Type Value
_dmarc TXT v=DMARC1; p=quarantine; pct=100; rua=mailto:dmarc@yourdomain.com

Start with p=none (monitoring only) for the first 2 weeks, then move to p=quarantine:

# Week 1-2: monitor only
v=DMARC1; p=none; rua=mailto:dmarc@yourdomain.com

# After confirming SPF and DKIM pass in reports:
v=DMARC1; p=quarantine; pct=100; rua=mailto:dmarc@yourdomain.com

2.4 Test email deliverability

Once DNS records propagate (up to 48 hours), test with:

  1. mail-tester.com — free, shows your score out of 10 and exactly what's failing
  2. MXToolbox — checks SPF, DKIM, DMARC, blacklists

Target score: 9/10 or higher before sending billing emails to customers.

2.5 Verify from the app

# Send a test billing email from Flask shell
cd /var/www/jqc
source venv/bin/activate
flask shell

from app.billing.emails import send_billing_email
send_billing_email(
    'your@email.com',
    'trial_ending',
    {
        'trial_ends_at':  'July 15, 2026',
        'subscribe_url':  'https://demo.jqc.app/billing/subscribe',
        'days_left':      3,
        'tenant_name':    'Test Tenant',
    }
)
exit()

Check that it arrives in the inbox (not spam) and renders correctly.


3. Cron Jobs — Final Setup

After going live, make sure all cron jobs including the new trial reminder are configured.

Edit the crontab for the jqc user:

sudo crontab -u jqc -e

Add all six jobs (replace YOUR_SECRET and yourdomain.jqc.app):

# SLA breach alerts — every 30 minutes
*/30 * * * * curl -s -X POST https://yourdomain.jqc.app/notifications/check-sla \
    -d "token=YOUR_DIGEST_SECRET" >> /var/www/jqc/logs/cron.log 2>&1

# Daily email digest — 7 AM
0 7 * * * curl -s -X POST https://yourdomain.jqc.app/notifications/send-digest \
    -d "token=YOUR_DIGEST_SECRET&frequency=daily" >> /var/www/jqc/logs/cron.log 2>&1

# Weekly email digest — Monday 7 AM
0 7 * * 1 curl -s -X POST https://yourdomain.jqc.app/notifications/send-digest \
    -d "token=YOUR_DIGEST_SECRET&frequency=weekly" >> /var/www/jqc/logs/cron.log 2>&1

# Facility score trend alerts — 8 AM daily
0 8 * * * curl -s -X POST https://yourdomain.jqc.app/notifications/check-score-trends \
    -d "token=YOUR_DIGEST_SECRET" >> /var/www/jqc/logs/cron.log 2>&1

# Scheduled reports — 8 AM daily
0 8 * * * curl -s -X POST https://yourdomain.jqc.app/scheduled-reports/run \
    -d "secret=YOUR_DIGEST_SECRET" >> /var/www/jqc/logs/cron.log 2>&1

# Expired API token cleanup — 3 AM daily
0 3 * * * curl -s -X POST https://yourdomain.jqc.app/notifications/cleanup-tokens \
    -d "token=YOUR_DIGEST_SECRET" >> /var/www/jqc/logs/cron.log 2>&1

# Trial-ending reminder emails — 9 AM daily  ← NEW
0 9 * * * curl -s -X POST https://yourdomain.jqc.app/notifications/trial-reminders \
    -d "token=YOUR_DIGEST_SECRET" >> /var/www/jqc/logs/cron.log 2>&1

# Per-tenant DB backup — 2 AM daily
0 2 * * * set -a; . /etc/jqc/control.env; set +a; \
    python -m control.backup --tenant all \
    --output-dir /var/backups/jqc >> /var/www/jqc/logs/cron.log 2>&1

Note on yourdomain.jqc.app: Use your tenant-zero subdomain (e.g. lts.jqc.app) or your custom domain (e.g. jqc.ltservicesinc.com). The trial-reminders endpoint is exempt from tenant middleware so any valid host works for it, but the others require a valid tenant host.


4. Final Pre-Launch Verification

Run through this checklist before announcing to customers:

Stripe

  • python -m control.cli list-plans shows price_live_… IDs (not price_test_…)
  • Stripe Dashboard → Webhooks shows endpoint is enabled in live mode
  • Made a real test payment through the subscribe flow using a live card
  • Webhook delivery log shows 200 OK responses
  • billing_email is set on tenant-zero (check: python -m control.cli list-plans)

Email

  • mail-tester.com score ≥ 9/10
  • SPF TXT record published and passes at mxtoolbox.com
  • DKIM TXT record published and passes
  • DMARC TXT record published (start with p=none)
  • Test billing email arrives in Gmail inbox, not spam
  • Test billing email HTML renders correctly (blue header, button, footer)

Trial reminder cron

  • Test manually: curl -X POST https://yourdomain.jqc.app/notifications/trial-reminders -d "token=YOUR_SECRET"
  • Response: {"ok": true, "sent": N, "skipped": N, "errors": 0}
  • Cron entry is in crontab -u jqc -l

Backup

  • python -m control.backup --tenant all --output-dir /var/backups/jqc runs without errors
  • /var/backups/jqc/ contains .sql.gz files for each tenant
  • Backup cron entry is in the crontab

5. Post-Launch Monitoring

Watch these in the first week after going live:

# App errors
tail -f /var/www/jqc/logs/jqc.log | grep -E 'ERROR|WARNING|BILLING'

# Cron job results
tail -f /var/www/jqc/logs/cron.log

# Stripe events (Stripe Dashboard → Developers → Events)
# Look for: checkout.session.completed, customer.subscription.updated

# Trial reminders sent
grep 'TRIAL REMINDERS' /var/www/jqc/logs/jqc.log