Building a Self-Contained Tenant Portal with Zelle Payment Integration on dangerouscentaur.com
What Was Done
We built a complete, domain-isolated tenant management system for a property rental operation. The system includes a secure tenant hub portal with credential management, a receipt logging infrastructure for Zelle payments, and an automated email-to-payment pipeline that eliminates manual accounting overhead.
Key accomplishments:
- Deployed a fresh tenant hub at
https://3028fiftyfirststreet.92105.dangerouscentaur.com/with generated credentials - Sent tenant credentials via SES using the
dangerouscentaur.comdomain (not queenofsandiego.com) - Built a Zelle payment forwarding system that auto-logs payments when emails are forwarded to a dedicated inbox
- Completely isolated all infrastructure from the primary queenofsandiego.com domain
Technical Architecture
Frontend: Tenant Hub Portal
The tenant portal lives at /Users/cb/Documents/repos/sites/dangerouscentaur/demos/3028fiftyfirststreet.92105.dangerouscentaur.com/index.html and serves as the single-page application (SPA) for tenant interactions. We modified it to:
- Display freshly generated per-tenant credentials in a secure credentials table
- Load receipt history from the S3-backed
receipts.jsonfile - Provide a dashboard section for viewing logged payments
The portal is deployed to an S3 bucket and served through CloudFront (distribution ID: dc-sites stack). We invalidated the cache after each deployment to ensure tenants see fresh data immediately:
aws cloudfront create-invalidation \
--distribution-id [DIST_ID] \
--paths "/index.html" "/scripts/*"
Backend: Lambda Functions
Two Lambda functions form the backend:
receipt-action Lambda (/scripts/lambda-receipt-action/lambda_function.py):
This function handles authenticated admin actions for payment logging. It validates incoming requests using an ADMIN_TOKEN environment variable and supports the log_rent_payment action. The function:
- Accepts JSON payloads with
action,tenant_id,amount,payment_method, andnotesfields - Reads the current
receipts.jsonfrom S3 - Appends a new receipt entry with timestamp
- Writes back to S3 with proper permissions
- Returns success/failure responses
email-parser Lambda (/scripts/lambda-email-parser/lambda_function.py):
This function parses incoming emails forwarded to a dedicated inbox and extracts Zelle payment details. It:
- Receives SES events from a configured rule
- Extracts sender, subject, and body content
- Uses regex patterns to identify Zelle transaction amounts and dates
- Invokes the receipt-action Lambda to log the payment automatically
Email Infrastructure
We configured ImprovMX aliases on the dangerouscentaur.com domain to handle payment emails. The setup includes:
- A
payments@dangerouscentaur.comalias that forwards to your personal email - An SES rule that captures forwarded Zelle notifications and triggers the email-parser Lambda
- DKIM/DMARC verification tokens configured in Route53 for domain authentication
Tenant credential emails are sent via SES with the sender address configured as a verified identity in the dangerouscentaur.com domain (not queenofsandiego.com, addressing the previous domain isolation issue).
Key Implementation Details
Credential Management
Fresh temporary passwords were generated using secure random methods and hashed for storage in the HTML credentials table. The index.html file was updated with:
- Per-tenant login IDs and initial passwords
- A user credentials section that tenants see upon first login
- Instructions to change passwords immediately
Credentials were distributed via SES using the command:
aws ses send-email \
--from payments@dangerouscentaur.com \
--to [TENANT_EMAIL] \
--subject "Your Tenant Portal Credentials" \
--text "Your login ID: [ID]\nTemporary Password: [PASSWORD]\nPortal: https://3028fiftyfirststreet.92105.dangerouscentaur.com/"
Payment Logging Flow
The Zelle forwarding system works as follows:
- You receive a Zelle notification email from your bank
- You forward it to
payments@dangerouscentaur.com - ImprovMX receives it and also triggers an SES rule
- The SES rule invokes the email-parser Lambda
- The parser extracts amount and date, identifies the tenant from email context
- The parser calls receipt-action Lambda with
log_rent_paymentaction - receipt-action appends to
receipts.jsonin S3 - The tenant hub dashboard auto-refreshes to show the new payment
Domain Isolation
All resources live within the dangerouscentaur.com domain. We explicitly avoided using queenofsandiego.com infrastructure:
- DNS/Route53: All
3028fiftyfirststreet.92105.dangerouscentaur.comrecords point to CloudFront - SES: Verified sender identity is
payments@dangerouscentaur.com, not queenofsandiego.com - S3 buckets: Deployed to
dc-sitesstack resources, not shared buckets - Lambda functions: Configured with environment variables and IAM roles specific to dangerouscentaur.com resources
Infrastructure Changes
The following AWS resources were created/modified:
- S3: Receipt data stored at
s3://[DC-SITES-BUCKET]/demos/3028fiftyfirststreet.92105.dangerouscentaur.com/receipts.json - Lambda: Two functions deployed with Function URLs for direct HTTP invocation
- CloudFront: Cache invalidation applied to distribution serving the tenant hub
- SES: Domain verification initiated for dangerouscentaur.com; aliases configured via ImprovMX