```html

Building a Domain-Isolated Tenant Portal with Automated Payment Logging via Email Forwarding

This post covers the implementation of a complete tenant management system for a rental property, with a focus on domain isolation, secure credential distribution, and an automated payment logging pipeline triggered by email forwarding. The system enables property managers to log Zelle payments by simply forwarding banking emails to a dedicated inbox, which automatically updates the tenant hub's payment records.

What Was Done

We built a multi-component system with three key objectives:

  • Deploy a tenant hub portal at 3028fiftyfirststreet.92105.dangerouscentaur.com completely isolated from the queenofsandiego.com domain
  • Generate secure temporary credentials for tenants and email them via the dangerouscentaur.com domain using SES
  • Create an email-driven payment logging system where forwarded Zelle receipts automatically update the tenant hub's payment records

The key insight was treating email forwarding as a valid API input method rather than a separate concern—this allowed us to leverage the existing Google Apps Script command infrastructure to process banking emails the same way we process other administrative actions.

Architecture Overview

The system consists of four main components working in concert:

  • Tenant Hub (Frontend): Static HTML + JavaScript deployed to S3 and served via CloudFront, containing user credentials table and receipts dashboard
  • Receipt Action Lambda: AWS Lambda function at `/scripts/lambda-receipt-action/lambda_function.py` handling authenticated payment log writes
  • Email Parser Lambda: AWS Lambda at `/scripts/lambda-email-parser/lambda_function.py` for parsing forwarded Zelle emails into structured payment data
  • Google Apps Script Handler: `WarmLeadResponder.gs` command processor that bridges email forwarding to Lambda invocation

The architecture intentionally avoids any cross-domain communication. All SES email sending uses dangerouscentaur.com, all S3 assets are in dangerouscentaur-specific buckets, and CloudFront distributions are domain-specific.

Technical Implementation Details

1. Tenant Hub Deployment

The tenant hub at /Users/cb/Documents/repos/sites/dangerouscentaur/demos/3028fiftyfirststreet.92105.dangerouscentaur.com/index.html was updated with:

  • A user credentials table containing tenant identifiers, temporary passwords, and account status
  • A receipts dashboard that loads payment records from the receipt-action Lambda via authenticated requests
  • Client-side logic to generate and validate admin tokens for API calls

The HTML file was deployed to S3 and served through CloudFront with cache invalidation to ensure immediate availability:

aws s3 cp index.html s3://dc-sites-dangerouscentaur-3028/ --cache-control "max-age=3600"
aws cloudfront create-invalidation --distribution-id [DIST_ID] --paths "/*"

2. SES Domain Configuration for dangerouscentaur.com

Rather than sending emails from queenofsandiego.com (which created domain isolation concerns), we properly configured dangerouscentaur.com as an SES sender identity:

  • Initiated domain verification through AWS SES console
  • Retrieved DKIM tokens and added them to the dangerouscentaur.com domain's DNS records via Namecheap
  • Set up ImprovMX alias to handle inbound mail routing to a monitored inbox
  • Verified the domain was in production SES (not sandbox) before sending tenant credentials

This required updating DNS records at the registrar to include SES DKIM CNAME records and MX records pointing to ImprovMX for inbound handling.

3. Credential Generation and Distribution

Fresh temporary passwords were generated using Python's secrets module and bcrypt hashing, then embedded directly into the hub's HTML credentials table. The flow was:

# In lambda_function.py
import secrets
import bcrypt

temp_password = secrets.token_urlsafe(16)
password_hash = bcrypt.hashpw(temp_password.encode(), bcrypt.gensalt()).decode()

Emails were sent via SES using boto3, with all sender/recipient addresses scoped to dangerouscentaur.com infrastructure:

aws ses send-email \
  --from credentials@dangerouscentaur.com \
  --to tenant@example.com \
  --subject "Tenant Hub Credentials - 3028 51st Street" \
  --html file://credentials_email.html

4. Receipt Action Lambda - Authenticated Payment Logging

The receipt-action Lambda at `/scripts/lambda-receipt-action/lambda_function.py` was enhanced with:

  • Admin token validation—any request without a valid ADMIN_TOKEN environment variable is rejected
  • A new log_rent_payment action that accepts tenant ID, amount, payment date, and method
  • S3 write operations to append payment records to receipts.json in the dangerouscentaur S3 bucket
  • CORS headers allowing the hub's JavaScript to make authenticated cross-origin requests

The Lambda URL was configured with no authorization at the API Gateway level—instead, authorization is enforced by checking the admin token within the function itself. This allows the hub frontend to make calls without AWS credentials.

5. Email Parser Lambda - Zelle Receipt Extraction

A new Lambda function at `/scripts/lambda-email-parser/lambda_function.py` handles the heavy lifting of parsing forwarded banking emails:

  • Receives raw email content (as forwarded to the dangerouscentaur.com inbox)
  • Extracts payment amount, transaction date, and sender details using regex patterns
  • Returns structured JSON with amount, date, and identified tenant
  • Logs any parsing failures for manual review
# Simplified extraction logic
import re

amount_match = re.search(r'\$(\d+\.\d{2})', email_body)
date_match = re.search(r'(\d{1,2})/(\d{1,2})/(\d{4})', email_body)

if amount_match and date_match:
    return {
        "amount": float(amount_match.group(1)),
        "date": date_match.group(0),
        "status": "parsed"
    }

6. Google Apps Script Email-to-Action Pipeline

The WarmLeadResponder.gs` file was extended with a new handler for payment logging:

  • When an email arrives at the dangerouscentaur inbox (via ImprovMX forwarding), Google Apps Script detects it
  • The script invokes the email parser Lambda to extract payment data
  • On successful parsing, it calls the receipt-action Lambda's log_rent_payment action with the extracted data and the admin token
  • The payment record is written to S3 and the hub's dashboard reflects it on next refresh