Building a Multi-Domain Tenant Portal with Automated Payment Logging via Email Forwarding
Overview
We built a complete tenant management system for a rental property, segregated entirely within the dangerouscentaur.com domain, with three key components: a secure tenant hub for credential management, a Lambda-based payment logging system, and an email-forwarding pipeline that automatically captures Zelle payments without manual intervention.
What Was Done
The project involved three interconnected systems:
- Deploying a tenant hub portal at
3028fiftyfirststreet.92105.dangerouscentaur.comwith generated credentials - Setting up SES email delivery from
dangerouscentaur.comdomain aliases to replace messages previously sent fromqueenofsandiego.com - Creating a Lambda-based payment intake system that processes Zelle email forwards and logs them to a receipts registry
Architecture & Infrastructure
Tenant Hub Portal
The hub is deployed to S3 with CloudFront acceleration. The portal lives at:
- S3 bucket:
3028fiftyfirststreet.92105.dangerouscentaur.com - Index file:
/index.html(contains embedded credentials table with bcrypt-hashed passwords) - CloudFront distribution: Configured to serve from the S3 bucket with cache invalidation on deploys
The hub includes:
- An embedded user credentials table (username/password pairs with hashed passwords for verification)
- A receipts dashboard that loads payment records via Lambda function URL
- Dynamic section loading based on authenticated user context
Lambda Functions
Two Lambda functions handle the backend logic:
- Receipt Action Lambda: Located at
/scripts/lambda-receipt-action/, handles admin actions includinglog_rent_payment - Email Parser Lambda: Located at
/scripts/lambda-email-parser/, processes incoming email forwards and triggers payment logging
Both are deployed with function URLs, allowing direct HTTPS invocation without API Gateway overhead. The receipt-action Lambda is protected via environment variable authentication token.
Email System
AWS SES handles all outbound email from the dangerouscentaur.com domain:
- Domain verified in SES with DKIM tokens configured in DNS
- Sender address: appropriate inbox alias for the domain (not specified here for security)
- All tenant credential emails sent from this verified identity
Incoming Zelle notifications are forwarded to an email-parsing endpoint, which triggers Lambda processing.
Technical Implementation Details
Password Generation & Storage
Tenant credentials were generated with bcrypt hashing. The process:
- Generate random alphanumeric passwords
- Hash passwords using bcrypt with appropriate salt rounds
- Embed hashed values in the portal HTML for client-side verification
- Distribute plaintext passwords via SES (one-time delivery, not stored in code)
This approach allows the portal to verify passwords without storing them in plaintext or requiring a database backend.
Payment Logging Pipeline
The payment intake system works as follows:
User sends Zelle payment → Bank sends confirmation email
→ User forwards email to dangerouscentaur domain alias
→ SES/Lambda triggers email-parser function
→ Parser extracts payment details (amount, date, payer)
→ Calls receipt-action Lambda with log_rent_payment action
→ Admin token validates the request
→ Payment logged to receipts.json in S3
→ Portal dashboard reflects new payment on next load
The receipt-action Lambda uses an environment variable ADMIN_TOKEN to authenticate internal calls, preventing unauthorized payment entries.
File Structure
The deployment structure in the repo:
/dangerouscentaur/demos/3028fiftyfirststreet.92105.dangerouscentaur.com/
├── index.html # Portal UI with embedded credentials
├── scripts/
│ ├── lambda-receipt-action/
│ │ └── lambda_function.py # Handles payment logging & admin actions
│ └── lambda-email-parser/
│ └── lambda_function.py # Processes forwarded emails
└── receipts.json # Source of truth for payments (in S3)
Deployment Pipeline
Updates to the portal follow this sequence:
- Update
index.htmllocally with new content or credentials - Deploy to S3:
aws s3 sync . s3://3028fiftyfirststreet.92105.dangerouscentaur.com/ - Invalidate CloudFront cache to ensure immediate propagation
- For Lambda updates: package function, deploy via AWS CLI, set environment variables
Lambda deployments include setting the ADMIN_TOKEN environment variable after initial deploy, ensuring the receipt-action function can validate internally-triggered actions.
Key Architectural Decisions
Why a static receipts.json instead of DynamoDB? This keeps the system simple and fully contained in the dangerouscentaur domain without external service dependencies. S3 provides sufficient durability and cost-effectiveness for a single-property rental use case.
Why embed passwords in HTML instead of a database? The tenant hub is read-only by design—tenants authenticate to view their section. Bcrypt hashing in HTML provides client-side verification without exposing plaintext credentials to the server.
Why email forwarding instead of manual logging? Email forwarding is a natural workflow for someone already handling bank notifications. Automating the parsing eliminates data-entry friction and ensures consistency in logging.
Why separate the domains completely? The queenofsandiego.com domain is for real estate agent business; dangerouscentaur.com is for property management. Segregation prevents credential leakage, simplifies compliance, and allows independent scaling of each system.
What's Next
Future enhancements could include:
- Automated rent reminders sent to tenants via SES
- Dashboard reporting for landlord view (payments, late fees, occupancy)
- Integration with other payment methods (ACH, credit card) beyond Zelle
- Encrypted file upload to the hub for lease agreements or maintenance requests
- Audit logging of all admin actions (payments logged, credentials issued)
The foundation is in place for all of these—the Lambda-based architecture makes adding new actions straightforward, and the SES integration provides the backbone for outbound communications.