Building a Self-Contained Tenant Payment Portal: Isolating dangerouscentaur.com from Legacy Systems
What Was Done
We completed a critical architectural separation: migrating a rental property tenant hub from shared infrastructure (queenofsandiego.com) to a completely isolated, property-specific domain (dangerouscentaur.com). This included deploying fresh tenant credentials, establishing a domain-native SES email pipeline, and building a Zelle payment forwarding system that lets the property manager forward bank notifications directly into the tenant accounting system without manual data entry.
The Problem: Architecture Leakage
The original system had a fundamental design flaw—tenant communications were being sent from queenofsandiego.com, a separate real estate entity's domain. This created:
- Credential and trust issues (wrong domain in tenants' inboxes)
- Operational risk (shared infrastructure, shared failure domains)
- Audit/compliance confusion (mixing multiple properties' data)
- Operational overhead (managing cross-domain dependencies)
The requirement was clear: complete isolation. All systems for 3028 51st Street must operate within dangerouscentaur.com exclusively.
Technical Architecture
Frontend: Tenant Hub Portal
The tenant hub lives at https://3028fiftyfirststreet.92105.dangerouscentaur.com/, served from S3 with CloudFront caching. The hub's index.html contains:
- A credentials table with tenant names, usernames, and temporary passwords (regenerated for security)
- A receipts section that loads payment data via the
loadReceipts()function - Dashboard initialization that populates tenant-specific sections
File path: /Users/cb/Documents/repos/sites/dangerouscentaur/demos/3028fiftyfirststreet.92105.dangerouscentaur.com/index.html
The portal was regenerated with fresh bcrypt-hashed passwords, redeployed to S3, and cached invalidated through CloudFront to ensure immediate propagation.
Backend: Lambda Functions
Two Lambda functions handle the core operations:
- Receipt Action Lambda (
/scripts/lambda-receipt-action/lambda_function.py): Handles payment logging with admin token authorization. The newlog_rent_paymentaction accepts tenant ID, amount, payment method, and notes, writing to the receipts data store. - Email Parser Lambda (
/scripts/lambda-email-parser/lambda_function.py): Processes inbound emails forwarded from the property manager's Zelle notifications, extracting payment data and triggering the receipt action.
Both Lambdas are deployed under the dangerouscentaur.com namespace and have Function URLs for direct invocation (no API Gateway overhead).
Email Infrastructure: SES + ImprovMX
The critical isolation move was establishing dangerouscentaur.com as the email sender:
- Domain verification initiated in AWS SES with DKIM token configuration
- ImprovMX catch-all alias set up for inbound mail routing:
*@dangerouscentaur.com→ dedicated receiving address - Outbound credentials emails sent via SES from
support@dangerouscentaur.com
This ensures all tenant communications originate from the property's own domain, eliminating the queenofsandiego.com confusion.
Payment Logging Pipeline: Zelle → Gmail → Lambda → Portal
The property manager can now forward Zelle payment notifications (either email or forwarded text) to the dangerouscentaur.com inbox. Here's the flow:
- Manager receives Zelle confirmation from their bank
- Forwards it to
*@dangerouscentaur.com(ImprovMX catch-all) - Email arrives at the designated receiving address (managed through ImprovMX forwarding rules)
- Google Apps Script (GAS)
WarmLeadResponder.gsmonitors the inbox - Custom regex pattern recognizes Zelle payment confirmations and extracts: tenant identifier, amount, timestamp
- GAS invokes the receipt-action Lambda with
log_rent_paymentaction and ADMIN_TOKEN - Lambda writes the payment record to receipts.json in S3
- Tenant hub's
loadReceipts()refreshes and displays the payment on their dashboard
This removes manual data entry entirely while keeping the system self-contained within dangerouscentaur.com.
Infrastructure Changes: Exact Resources
- S3 Bucket: Hosts the tenant portal HTML and receipts.json data file
- CloudFront Distribution: Caches the 3028fiftyfirststreet subdomain with invalidation rules for rapid updates
- Lambda Functions: Receipt-action and email-parser deployed with environment variables for ADMIN_TOKEN and S3 bucket paths
- SES Domain: dangerouscentaur.com added to verified identities with DKIM signing enabled
- ImprovMX Configuration: Catch-all rule for *.dangerouscentaur.com routing to receiving inbox
- Route53: DNS records updated to point 3028fiftyfirststreet.92105.dangerouscentaur.com to CloudFront distribution
- Google Apps Script: WarmLeadResponder.gs updated with Zelle payment parsing logic and Lambda invocation code
Key Technical Decisions
Why ImprovMX + GAS instead of SNS + SQS? The property manager already has a Gmail inbox; forwarding is a familiar, low-friction operation. ImprovMX provides simple catch-all routing without additional AWS costs. Google Apps Script integrates naturally with Gmail and Lambda's HTTP endpoints, keeping the architecture lightweight.
Why admin token on the Lambda? The receipt-action Lambda is exposed via public Function URL. The ADMIN_TOKEN environment variable ensures only authorized callers (GAS scripts we control) can execute payment-logging actions, preventing arbitrary payment recording.
Why bcrypt-hashed passwords in HTML? Client-side validation only—the HTML credentials table is for the manager's reference. Actual tenant authentication would use a separate, server-side credentials system (implemented in follow-up phases).
Why receipts.json in S3? Simple, durable data store. CloudFront can cache it with appropriate headers. No database operational overhead for this property-specific data.
Deployment Commands (Sanitized)
# Redeploy tenant portal with fresh credentials
aws s3 cp index.html s3://[bucket]/3028fiftyfirststreet.92105.dangerouscentaur.com/index.html --cache-control "max-age=300"
# Invalidate CloudFront cache
aws cloudfront create-invalidation --distribution-id [DIST_ID] --paths "/*"
# Send credentials email via SES
aws ses send-email --from support@dangerouscentaur.com --to [tenant-email] --subject "