Building a Self-Contained Payment Logging System: Isolating Tenant Portal Infrastructure from Marketing Domain
What Was Done
We implemented a complete infrastructure separation for a property management tenant portal, moving it from the marketing domain (queenofsandiego.com) to a dedicated dangerouscentaur.com domain. This included:
- Provisioning a new SES sender identity for dangerouscentaur.com with proper DKIM validation
- Creating a Zelle payment forwarding pipeline that captures email receipts and logs them directly to the tenant hub
- Implementing an admin-authenticated Lambda function endpoint for payment logging
- Wiring a Google Apps Script handler to intercept forwarded bank emails and trigger automated logging
- Regenerating tenant credentials and redeploying the portal under the new domain
Technical Details: Domain Isolation
The critical architectural decision was complete domain separation. Previously, credential emails were being sent from queenofsandiego.com—inappropriate for a tenant-facing system. The new flow uses dangerouscentaur.com exclusively:
File: /sites/dangerouscentaur/demos/3028fiftyfirststreet.92105.dangerouscentaur.com/index.html
Purpose: Tenant portal hub (deployed to S3 + CloudFront)
Contains: User credentials table, receipts section, payment logging UI
The tenant portal itself runs at https://3028fiftyfirststreet.92105.dangerouscentaur.com/ as a subdomain of dangerouscentaur.com, ensuring all communication appears domain-appropriate to tenants.
Infrastructure: SES Configuration
Email identity setup required explicit dangerouscentaur.com configuration in AWS SES:
- Verified Domain: Initiated domain verification in SES for dangerouscentaur.com
- DKIM Tokens: Retrieved DKIM tokens from AWS and added them to Namecheap DNS (the domain registrar)
- Email Aliases: Set up ImprovMX forwarders to create a functional inbox for the dangerouscentaur.com domain (ensuring replies don't bounce)
- Whitelist Check: Verified current IP against Namecheap's email server whitelist to prevent deliverability issues
This prevents the "scary error messages" that occur when sending from unverified domains in SES.
Lambda Architecture: Payment Logging Endpoint
We created a new protected Lambda endpoint for recording Zelle payments:
File: /sites/dangerouscentaur/demos/3028fiftyfirststreet.92105.dangerouscentaur.com/scripts/lambda-receipt-action/lambda_function.py
Function Name: log_payment (new action handler)
Trigger: HTTP via Lambda Function URL
Auth: Custom ADMIN_TOKEN header validation
The function accepts a JSON payload with payment details and appends records to S3:
POST /lambda-receipt-action/
Headers: {"Authorization": "Bearer <ADMIN_TOKEN>"}
Body: {
"action": "log_payment",
"payment_method": "zelle",
"amount": 2500.00,
"tenant_id": "3028_51st",
"timestamp": "2024-01-15T14:32:00Z"
}
The Lambda reads the current receipts.json from S3, appends the new payment record, and writes it back. This keeps the receipts section of the tenant hub automatically updated without manual data entry.
Google Apps Script: Email Forwarding Pipeline
To eliminate manual logging, we wired the existing Apps Script infrastructure to intercept bank emails:
File: /sites/queenofsandiego.com/WarmLeadResponder.gs
New Handler: Zelle email detection in the command routing logic
When a forwarded Zelle email arrives at the Apps Script-monitored inbox, the script:
- Parses the email body for payment amount and timestamp using regex patterns matching Zelle receipt format
- Extracts tenant identifier from the email subject or thread context
- Calls the
log_paymentLambda endpoint with the ADMIN_TOKEN - Logs the result back to a spreadsheet for audit purposes
This creates a completely automated flow: forward bank receipt → payment logged to hub, with zero manual intervention.
Credential Regeneration & Deployment
Fresh passwords were generated for both tenants and stored in the tenant credentials table within the hub HTML:
File: /sites/dangerouscentaur/demos/3028fiftyfirststreet.92105.dangerouscentaur.com/index.html
Changes: Updated password hashes in the user credentials table
Deployment:
1. Uploaded to S3 bucket: <tenant-portal-s3-bucket>
2. Invalidated CloudFront distribution cache (full /*)
Emails were then sent via SES from dangerouscentaur.com (not queenofsandiego.com) containing the portal URL and new credentials.
Key Decisions & Rationale
- Why Lambda Function URLs instead of API Gateway? Lower operational overhead, direct HTTP endpoints, simpler CORS handling for front-end payment logging UI, and easier to maintain with minimal infrastructure.
- Why parse bank emails in Apps Script instead of a dedicated service? We already had a working Apps Script infrastructure with email monitoring. Reusing it reduces deployment complexity and keeps the system consolidated in GCP, which is already running the domain's email forwarding.
- Why store receipts as JSON in S3 instead of a database? This is a low-volume system (a few payments per month per property). JSON in S3 is simpler to operate, version-controlled via CloudFront distribution versioning, and requires no database maintenance.
- Why full domain separation? Tenant-facing systems should never appear to originate from marketing domains. This prevents confusion, improves deliverability reputation, and cleanly separates concerns—accounting infrastructure is completely independent from lead gen and sales systems.
What's Next
The system is now production-ready for the 3028 51st Street property. Future enhancements could include:
- Multi-property scaling by templating the dangerouscentaur subdomain structure for additional properties
- Expense logging via the same pipeline (forward receipts, auto-categorize into operating expenses)
- Receipt image storage in S3 with references in the JSON records for audit trails
- Tenant notifications via email when payments are logged (adds confirmation step)