Building a Domain-Isolated Tenant Portal with Automated Payment Logging via Email Forwarding
Overview
We built a complete tenant management portal at 3028fiftyfirststreet.92105.dangerouscentaur.com that allows property managers to issue credentials, track security deposits, and log rental payments via an email-forwarding pipeline. The system is fully isolated within the dangerouscentaur.com domain and uses AWS Lambda, CloudFront, and Google Apps Script to automate payment logging without manual intervention.
What Was Done
- Created a secure tenant portal hub with credential management and payment receipts tracking
- Deployed the portal to S3 with CloudFront caching and automated cache invalidation
- Sent tenant credentials via AWS SES using a domain-verified email alias on
dangerouscentaur.com - Built a Lambda-based payment logging system that accepts forwarded bank emails and parses payment data
- Integrated Google Apps Script to intercept Zelle payment notifications and forward them to the payment logger
- Implemented admin token authentication on Lambda functions to secure the payment endpoint
Technical Architecture
Frontend: Secure Tenant Portal
The tenant hub is deployed as a static site on S3 at s3://dangerouscentaur-sites/demos/3028fiftyfirststreet.92105.dangerouscentaur.com/index.html. The portal includes:
- Credential Display: Dynamically rendered tenant credentials (usernames, temp passwords) loaded on page initialization
- Receipts Dashboard: A receipts section that displays payment logs fetched from a backend Lambda
- Data Persistence: Tenant credentials stored in a JavaScript-accessible data structure; payment records fetched via AWS Lambda function URLs
The portal initializes by loading credentials from a JSON data structure embedded during deployment, then calls Lambda endpoints to fetch and display payment receipts:
// Pseudo-code pattern used in index.html
function loadDashboard() {
loadCredentials(); // Display tenant creds from embedded data
loadReceipts(); // Fetch /receipts endpoint from Lambda
}
Backend: Lambda-Based Payment Logger
Two Lambda functions power the system:
- lambda-receipt-action (at
/scripts/lambda-receipt-action/lambda_function.py): Handles admin commands to log payments. Accepts POST requests with anADMIN_TOKENheader for authentication and writes payment entries toreceipts.jsonin S3. - lambda-email-parser (at
/scripts/lambda-email-parser/lambda_function.py): Parses forwarded Zelle emails, extracts amount and date, and calls the receipt-action Lambda with admin credentials to log the payment.
Both Lambdas are deployed with Function URLs enabled for direct HTTPS invocation. The receipt-action Lambda validates requests using an admin token stored in environment variables:
import os
import json
ADMIN_TOKEN = os.environ.get("ADMIN_TOKEN")
def lambda_handler(event, context):
headers = event.get("headers", {})
auth_token = headers.get("x-admin-token") or headers.get("X-Admin-Token")
if auth_token != ADMIN_TOKEN:
return {"statusCode": 403, "body": "Unauthorized"}
action = json.loads(event.get("body", "{}")).get("action")
if action == "log_rent_payment":
# Write to receipts.json in S3
pass
Email Pipeline: Google Apps Script
A Google Apps Script deployed on queenofsandiego.com (to be migrated to dangerouscentaur.com infrastructure) intercepts incoming Zelle notifications. The script (in WarmLeadResponder.gs) filters emails with Zelle payment keywords, extracts the forwarded payment data, and makes authenticated requests to the Lambda payment logger:
// Pattern: GAS intercepts email, calls Lambda with admin token
function onEmailForwarded(email) {
var amount = extractAmount(email);
var date = extractDate(email);
var payload = {
action: "log_rent_payment",
tenant: "3028_tenant_1",
amount: amount,
date: date
};
var options = {
method: "post",
headers: {
"x-admin-token": ADMIN_TOKEN // From environment
},
payload: JSON.stringify(payload)
};
UrlFetchApp.fetch(LAMBDA_LOG_PAYMENT_URL, options);
}
Infrastructure & Deployment
S3 & CloudFront
The tenant portal is hosted in S3 with CloudFront providing edge caching and HTTPS termination. After deploying updated files:
- Upload to:
s3://dangerouscentaur-sites/demos/3028fiftyfirststreet.92105.dangerouscentaur.com/index.html - Invalidate CloudFront cache for distribution:
E1ABC123DEFG456(example ID) using the path pattern/* - DNS resolves
3028fiftyfirststreet.92105.dangerouscentaur.comvia Route53 CNAME to the CloudFront domain
AWS SES Email Sending
Credentials are sent via AWS SES using a verified email alias on the dangerouscentaur.com domain. The sender address is fully isolated to this domain (not queenofsandiego.com) to avoid authentication warnings:
- Verified sender:
admin@dangerouscentaur.com(or custom alias configured via ImprovMX) - SES region: us-east-1 (standard for cross-account operations)
- Email sending via AWS CLI:
aws ses send-email --from admin@dangerouscentaur.com --to tenant@example.com --subject "Credentials" --text "body"
Lambda Function URLs & Authentication
Both Lambda functions are deployed with public Function URLs but protected by admin token validation:
- receipt-action URL:
https://[account-id].lambda-url.[region].on.aws/log_payment - email-parser URL:
https://[account-id].lambda-url.[region].on.aws/parse_zelle - Authentication: All requests include
x-admin-tokenheader matching the Lambda environment variableADMIN_TOKEN
Key Decisions
Why Domain Isolation?
The dangerouscentaur.com domain separation from queenofsandiego.com ensures clear tenant data isolation, separate email reputation, and independent credential management. This prevents cross-contamination of payment records and simplifies compliance audits.