Building a Self-Contained Payment Logging System for Multi-Tenant Property Management
We recently built a complete payment tracking system for a property management application that needed to live entirely within the dangerouscentaur.com domain—completely isolated from the primary business domain. The system needed to do three things: provision tenant credentials securely, allow payment logging via forwarded bank emails, and maintain an audit trail without manual intervention.
The Architecture Challenge
The tenant portal at https://3028fiftyfirststreet.92105.dangerouscentaur.com/ needed a payment verification workflow. Tenants would pay via Zelle (a peer-to-peer payment service), and we needed a way to:
- Log incoming payments without manual data entry
- Keep all communication within the
dangerouscentaur.comdomain - Maintain a tamper-evident audit trail in S3
- Avoid any cross-domain dependencies
The core issue: email coming from queenofsandiego.com to tenants was a domain leakage problem. We needed a clean, self-contained email infrastructure.
Phase 1: Tenant Credential Generation and Distribution
We modified the tenant hub index file at /Users/cb/Documents/repos/sites/dangerouscentaur/demos/3028fiftyfirststreet.92105.dangerouscentaur.com/index.html to include a credentials table with freshly generated temporary passwords. Each password was hashed using bcrypt and stored in the hub's initialization data.
The deployment pipeline:
# Build updated index.html with credential table
# Upload to S3 bucket: dangerouscentaur-property-demos
aws s3 cp index.html s3://dangerouscentaur-property-demos/3028fiftyfirststreet.92105/index.html
# Invalidate CloudFront distribution
aws cloudfront create-invalidation \
--distribution-id [DIST_ID] \
--paths "/*"
For email delivery, we configured SES (Simple Email Service) to send from a proper @dangerouscentaur.com alias. Instead of using a human inbox, we set up ImprovMX to forward mail destined for payments@dangerouscentaur.com to a Lambda function endpoint.
Phase 2: Email-to-Lambda Pipeline for Payment Receipts
We created a new Lambda function at /Users/cb/Documents/repos/sites/dangerouscentaur/demos/3028fiftyfirststreet.92105.dangerouscentaur.com/scripts/lambda-email-parser/lambda_function.py. This function:
- Receives forwarded Zelle payment confirmations via SES
- Parses the email body to extract payment amount, timestamp, and tenant reference
- Calls the receipt-action Lambda with an admin token to log the payment
- Stores the email body as evidence in S3 for audit purposes
The email-parser Lambda doesn't store data directly—it acts as a translator between the email format and the internal payment logging API.
Phase 3: Receipt Action Lambda with Admin Token Protection
We extended the existing receipt-action Lambda at /Users/cb/Documents/repos/sites/dangerouscentaur/demos/3028fiftyfirststreet.92105.dangerouscentaur.com/scripts/lambda-receipt-action/lambda_function.py with a new log_rent_payment action.
The key architectural decision: all payment logging requires an ADMIN_TOKEN environment variable. This token is:
- Set during Lambda deployment, never stored in code
- Required in the
Authorizationheader for any payment mutation - Different from tenant-facing API tokens (which are read-only)
- Rotatable without redeploying the Lambda
The payment logging function signature:
def log_rent_payment(tenant_id, amount, payment_method, receipt_email_key):
"""
Log a rent payment to the receipts.json audit trail.
Args:
tenant_id: Property identifier (e.g., "3028_51st")
amount: Payment amount in cents
payment_method: "zelle" | "check" | "ach"
receipt_email_key: S3 key to the forwarded email for evidence
Returns:
Updated receipts.json entry with timestamp and signature
"""
The receipts are stored in S3 at s3://dangerouscentaur-property-demos/3028fiftyfirststreet.92105/receipts.json as an append-only log with cryptographic signatures.
Phase 4: Google Apps Script Integration for Email Forwarding
We modified /Users/cb/Documents/repos/sites/queenofsandiego.com/WarmLeadResponder.gs to add a command handler for payment logging. However—and this is critical—the actual payment logging calls the dangerouscentaur.com Lambda endpoint, not any queenofsandiego.com infrastructure.
The GAS script now:
- Listens for emails forwarded to a
@dangerouscentaur.comaddress - Parses Zelle confirmation language
- Extracts tenant ID from the email metadata
- Calls the receipt-action Lambda with the ADMIN_TOKEN
- Logs the action in a local audit sheet within the
dangerouscentaur.comdomain
Why This Architecture?
Domain isolation: The tenant never receives mail from or connects to queenofsandiego.com. All communication is @dangerouscentaur.com. This matters for deliverability, compliance, and operational separation.
Email forwarding instead of manual entry: Rather than asking the property manager to type payment details into a form, they can forward their bank notification directly. The system parses it automatically, reducing data-entry errors.
Append-only receipts log: Instead of updating a mutable payments database, we append timestamped entries to receipts.json`. This creates an immutable audit trail that's easy to export and reconcile with bank statements.
Admin token separation: The Lambda has two token classes: tenant tokens (for reading their own data) and admin tokens (for recording payments). This prevents tenant-facing endpoints from accidentally logging payments.
Infrastructure Resources
- S3 bucket:
dangerouscentaur-property-demos(stores HTML, receipts.json, email archives) - CloudFront distribution: Fronts the property demo site for TLS termination and caching
- Lambda functions:
lambda-receipt-action(logs payments, returns receipt)lambda-email-parser(translates SES events to receipt-action calls)
- SES configuration: Domain verified for