Debelu infrastructure migration
This release keeps Supabase PostgreSQL, Auth, Realtime and private Storage, Paystack, the Railway API, and the Vercel-hosted Next.js marketing site. It prepares Cloudflare Pages for the Vite storefront/admin, R2 for new product images, and Amazon SES for notification and authentication email. Existing Supabase product image URLs remain valid; do not delete the old bucket.
1. Cloudflare Pages
The two Direct Upload Pages projects now exist. On 2026-09-28, the release-preview branch was deployed to both projects; on 2026-09-30 the verified production release of commit a4e85df5 deployed to their main branches. The stable preview URLs are https://release-preview.debelu-storefront.pages.dev and https://release-preview.debelu-admin.pages.dev. Their public and protected routes returned HTTP 200 or the expected login redirect, and both expose noindex headers. These are previews, not production custom-domain cutovers.
| Project | Production branch | Build output | Domain |
|---|---|---|---|
debelu-storefront | main | apps/storefront/dist | app.debelu.com |
debelu-admin | main | debelu-admin/dist | admin.debelu.com |
The release workflow .github/workflows/storefront-release.yml builds both apps at the monorepo root and uploads them with Wrangler. Add the GitHub secrets CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN (Pages Edit scope), then populate the VITE_* build variables already listed in the workflow. The repository already has the Supabase URLs/anon keys, Railway fallback API URL, Sentry DSNs, test Paystack public key, and VITE_R2_PRODUCT_IMAGES=false saved for CI. A narrowly scoped Pages token was added to GitHub Actions on 2026-09-28; its secret value was not shared in chat. The existing GitHub Production environment has no approval rule yet; the production job is main-branch-only, opt-in, and rejects a Paystack test key. A live public key was saved as an environment secret on 2026-09-30. The release passed preview verification and deployed to https://debelu-storefront.pages.dev and https://debelu-admin.pages.dev; both served the same live public key, with no test key in their entry bundles. The _redirects and _headers files in each app's public/ directory preserve SPA routes and security headers. The exact preview origins https://release-preview.debelu-storefront.pages.dev and https://release-preview.debelu-admin.pages.dev were added to Railway's ALLOWED_ORIGINS and deployed on 2026-09-28. OPTIONS requests from each origin now return HTTP 204 with the matching access-control-allow-origin. The backend release also explicitly permits the two production Pages origins, https://debelu-storefront.pages.dev and https://debelu-admin.pages.dev, without permitting arbitrary pages.dev subdomains. Verify their OPTIONS responses after Railway deploys the backend commit. The owner reports adding Supabase Auth redirect patterns for these two production Pages URLs on 2026-09-30; test login redirects there after deploy. Supabase Auth now contains both preview redirect patterns and https://admin.debelu.com/**; the malformed admin.debelu.com/** entry was removed. This was verified in the production project's URL Configuration on 2026-09-28. The production workflow checks its newly deployed pages.dev URLs; it does not claim the custom domains are already cut over. The Vercel production storefront and admin were also redeployed with that same live public key and their served bundles were checked on 2026-09-30. Point the two domains to Pages only after preview and production-deployment checks pass, then verify the custom domains separately. As of 2026-09-27, debelu.com has a pending Cloudflare zone but its authoritative nameservers still point to Vercel; custom-domain cutover requires a record-by-record review. The staged-zone record inventory and cutover checklist are in docs/dns-migration-plan.md; no nameservers have been changed.
Earlier preview builds used the Railway fallback hostname while api.debelu.com had the wrong TLS certificate. On 2026-09-28, after the approved CNAME and Railway verification TXT change propagated, https://api.debelu.com/health returned HTTP 200 with valid TLS and healthy Redis/Supabase checks. Both Pages preview origins received HTTP 204 on API CORS preflight. GitHub's VITE_API_BASE_URL and NEXT_PUBLIC_API_BASE_URL were therefore changed to https://api.debelu.com/api. A refreshed Pages preview build passed its browser smoke test and embeds that URL. The Railway fallback remains available for rollback.
The Next.js marketing application stays on Vercel. It uses live server-side routes and Supabase session cookies; a later Cloudflare Workers move needs its own compatibility and SEO test, including /store/[slug], authentication, redirects, headers, and image handling.
2. R2 product images
The debelu-product-images R2 bucket was created on 2026-09-27 and remains private. Connect cdn.debelu.com as its public custom domain only after debelu.com is managed by the same Cloudflare account: Cloudflare currently rejects the domain as absent from this account. This requires a separate DNS cutover decision; do not enable the R2 development URL as a production substitute. Create an R2 API token scoped to Object Read & Write for that bucket. Set R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_PUBLIC_BUCKET, and R2_PUBLIC_URL=https://cdn.debelu.com on Railway. Keep these credentials server-only.
The API accepts uploads only from authenticated sellers or staff, checks the actual file body against the 5 MB limit, places it in R2, and returns a cdn.debelu.com URL. This sends each upload through Railway; public image downloads come directly from Cloudflare. Test JPEG, PNG, WebP, and GIF from both web and a mobile build, then set VITE_R2_PRODUCT_IMAGES=true for the storefront build. The switch is off by default. A failed R2 upload produces an error and never saves an unusable product URL. Existing products continue reading from Supabase.
If R2 needs to be rolled back, rebuild the storefront with VITE_R2_PRODUCT_IMAGES=false. Keep R2 and its CDN domain serving any images already referenced by products. Moving old images or retiring Supabase's product-images bucket requires a separate inventory and URL rewrite.
3. Amazon SES
The production Supabase project now runs create-user, paystack-webhook (Paystack HMAC verification; JWT gateway disabled), and deliver-notification (JWT gateway enabled). The retired verify-paystack, create-virtual-account, and delete-account functions were removed after the new backend routes returned the expected authenticated response; resolve-bank was already absent. Their previous source was saved in a local rollback backup. Notification delivery is not attached to a database webhook yet: the required channel credentials and shared webhook secret are not configured, so it must not claim live sends.
The user selected London (eu-west-2), where the debelu.com sending identity has been created and verified. Publish the exact DKIM, custom MAIL FROM, and DMARC records in docs/dns-migration-plan.md at both the current authoritative Vercel DNS and the staged Cloudflare zone. Obtain SES production access and set up bounce and complaint monitoring before sending customer mail.
Meta confirms Debelu's WhatsApp number +234 916 153 1288 is connected (Phone Number ID 1375907198930745). Four category-specific utility templates for order, payment, dispute, and security updates were submitted on 2026-09-28; the owner reported their approval on 2026-09-30. WhatsApp delivery code in the Supabase Edge Function now selects those names by category. Production delivery still requires template app publication, webhook/billing setup, opt-in evidence, and a controlled send. The server-only WHATSAPP_ACCESS_TOKEN, WHATSAPP_PHONE_ID, and WHATSAPP_GRAPH_API_VERSION=v25.0 secret names were verified in Supabase on 2026-09-30. The legacy database default was opt-out; 23_whatsapp_consent.sql resets it to opt-in and clears old enabled values so members must switch WhatsApp on themselves. No customer WhatsApp sends should be enabled until those are in place and a controlled test succeeds. A signed Meta webhook callback is live at https://api.debelu.com/api/whatsapp/webhook; an incorrect verification token returns 403. Railway has not been given WHATSAPP_WEBHOOK_VERIFY_TOKEN or META_APP_SECRET, so Meta callback verification and app publication remain on hold. Inbound replies and delivery receipts are not processed yet, so do not subscribe the messages webhook field.
The storefront and admin have separate Sentry browser SDKs; the release workflow expects VITE_STOREFRONT_SENTRY_DSN and VITE_ADMIN_SENTRY_DSN. The Next.js marketing app also has browser, Node.js and Edge initialization for its own project. Session replay and performance tracing remain off pending a privacy review. The Railway API captures unhandled HTTP 500 exceptions using the user-supplied public backend DSN in production, with SENTRY_DSN available as an override. It sends a request ID but not request bodies, headers, cookies, or user emails. Create separate Sentry projects for marketing, storefront, admin and API, then confirm one controlled test event in each project. The user-supplied storefront and admin DSNs are saved in GitHub Actions variables and deployed to the Pages previews. The admin DSN is also configured on its Vercel production deployment. The backend DSN was supplied and a labelled local test event flushed through the Sentry transport. Its Railway variable is still missing, but the production fallback allows error capture on the deployed backend. Marketing's DSN is still missing.
Create an IAM identity limited to ses:SendEmail for the verified identity. On the Supabase deliver-notification Edge Function set SES_REGION, SES_ACCESS_KEY_ID, SES_SECRET_ACCESS_KEY, EMAIL_FROM, and optionally EMAIL_REPLY_TO and SES_CONFIGURATION_SET (the SES configuration set that publishes delivery, bounce, and complaint events). Deploy the function from this commit. Its delivery record marks an email sent when SES accepts it; bounced or complained-about mail must be monitored through SES separately.
The owner reported that Supabase Auth custom SMTP was connected to SES on 2026-09-30. Verify confirmation and recovery delivery before relying on it. Those SMTP credentials are distinct from the IAM API credentials above. Use the same verified transactional domain, raise Supabase's Auth email limit for expected signup traffic, and test signup confirmation and password reset. SES sending does not create a support mailbox; keep [email protected] and other published inboxes with an inbound email provider.
After the first successful SES deliveries, remove the old RESEND_API_KEY secret from Supabase and update the public subprocessors page to reflect the services actually processing Debelu data. Retain rollback access until SES mail flows and complaint monitoring are confirmed.
Cutover checks
- Run the normal CI suite and deploy Pages previews.
- Verify storefront deep links, admin deep links, payment checkout, login redirects, and native app links against the preview deployment.
- Verify one R2 product upload and public image delivery, while confirming an older Supabase product image still loads.
- Send Auth confirmation, password reset, order, and dispute messages via SES; inspect delivery, bounce, and complaint events.
- Change production domains and enable R2 uploads only after these checks.
Remaining integration audit
- Authentication: Keep Supabase Auth as the identity provider. Configure its separate SES SMTP connection, redirect allowlist, email limits, and confirmation/password-reset tests before moving any auth domain.
- Payments: Keep Paystack and preserve its webhook URL and signature secret; run a test checkout, webhook, refund, and payout before/after DNS cutover. Never point payment callbacks to an untested Pages preview.
- Inbound mail: Zoho is the selected provider. The owner confirmed the mailboxes were tested, and the two competing ImprovMX MX records were removed from authoritative Vercel DNS on 2026-09-30. Preserve all three Zoho MX, Zoho verification, DKIM, and SPF records in staged Cloudflare DNS. The root SPF still includes ImprovMX pending confirmation it is not used for sending.
- Database and files: Keep Supabase Postgres, Auth, private Storage, and existing product image URLs. Confirm backup/restore practice and a rollback snapshot before any later data movement.
- Push and messaging: Preserve FCM/APNs/VAPID configuration; WhatsApp needs a Meta app, sender, approved template, opt-in, and test before activation.
- Observability: Configure separate Sentry projects/DSNs and alert owners; verify API, storefront, SES bounce/complaint, Paystack webhook, and Pages deployment failures are observable.
- Access and bills: Review least-privilege AWS/Cloudflare credentials, GitHub production approval, the Truehost unpaid-invoice warning, and the domain's unlocked status. Do not enable production sends or switch DNS while any required account is unverified or at risk of interruption.