```html

Building a Self-Service Tenant Portal with Email-to-Payment Logging: Zelle Integration via AWS Lambda and Google Apps Script

What Was Done

We built a complete self-service tenant management system for a rental property, divorced entirely from existing business infrastructure. This included deploying a secure tenant hub portal at 3028fiftyfirststreet.92105.dangerouscentaur.com, establishing credential delivery via AWS SES from a dedicated domain, and creating an automated payment logging pipeline that accepts forwarded Zelle receipts via email.

The system allows property managers to:

  • Generate and distribute tenant credentials securely
  • Forward Zelle payment receipts to an email address, which automatically logs them in the tenant hub
  • Maintain complete domain separation from other business systems
  • Track rent payments without manual data entry

Architecture Overview

The solution spans three primary AWS services and Google Apps Script:

  • S3: Hosts the tenant hub static site with embedded credential management
  • CloudFront: Caches and serves the portal; handles cache invalidation on updates
  • Lambda: Two functions handle receipt logging and email parsing for payment ingestion
  • SES: Sends credential emails and receives forwarded payment receipts
  • Google Apps Script: Parses inbound emails and invokes Lambda payment logging

Technical Implementation

Tenant Hub Portal Structure

The tenant hub lives in /Users/cb/Documents/repos/sites/dangerouscentaur/demos/3028fiftyfirststreet.92105.dangerouscentaur.com/index.html. This single HTML file contains embedded credential tables secured by localStorage-based authentication. The portal is deployed to an S3 bucket (referenced via CloudFront distribution) and serves as the central interface for tenants to view their account status and payment history.

The hub includes a receipts section that dynamically loads from a backend API. When a payment is logged via the Lambda function, the receipts.json file (stored in S3) is updated, and the CloudFront cache is invalidated so tenants see fresh data immediately.

Credential Generation and Delivery

Fresh temporary passwords are generated using a combination of cryptographic hashing and random string generation. The credentials are:

  • Generated with unique per-tenant identifiers
  • Hashed using SHA-256 and stored in the portal HTML
  • Delivered via AWS SES from noreply@dangerouscentaur.com (established as a verified sender identity)
  • Included in credential emails with portal access instructions

Example SES send command (no credentials shown):

aws ses send-email \
  --from noreply@dangerouscentaur.com \
  --to tenant@example.com \
  --subject "Your Tenant Portal Credentials" \
  --html "Your credentials are: [USERNAME] / [HASHED_PASSWORD]"

Payment Logging Pipeline

The payment logging system works in reverse: instead of tenants entering data, the property manager forwards Zelle receipts to a dedicated email address. This email is received by Google Apps Script running in the queenofsandiego.com Apps Script project but configured to communicate exclusively with the dangerouscentaur.com infrastructure.

The flow:

  1. Manager forwards Zelle email to payments@dangerouscentaur.com (via ImprovMX alias)
  2. Google Apps Script receives the email via WarmLeadResponder.gs email handler
  3. Script parses the Zelle receipt for amount, date, and tenant identifier
  4. Script invokes Lambda function URL at the log_payment endpoint with admin token
  5. Lambda validates the admin token, appends payment to receipts.json
  6. Lambda triggers CloudFront cache invalidation
  7. Tenant sees updated payment record within seconds

Lambda Functions

Receipt Action Lambda (/scripts/lambda-receipt-action/lambda_function.py):

This function serves as the administrative backend for the tenant hub. It validates incoming requests using an environment variable ADMIN_TOKEN and processes actions like log_payment. The function:

  • Validates the Authorization header against ADMIN_TOKEN
  • Reads the current receipts.json from S3
  • Appends new payment entries with timestamp and tenant ID
  • Writes updated JSON back to S3
  • Returns a CloudFront invalidation request to clear cached receipts

Example payload structure (no secrets):

{
  "action": "log_payment",
  "tenant_id": "tenant_001",
  "amount": 1500.00,
  "date": "2024-01-15",
  "method": "zelle"
}

Email Parser Lambda (/scripts/lambda-email-parser/lambda_function.py):

A secondary function that could be invoked directly by SES or called from GAS. It's structured to parse email bodies, extract Zelle receipt details (amount, confirmation number, date), and format them for submission to the receipt-action Lambda.

Google Apps Script Integration

The WarmLeadResponder.gs script was extended with a new email handler that:

  • Watches for emails forwarded from the property manager's bank
  • Identifies Zelle receipt characteristics (keywords, structure)
  • Extracts: amount, date, payer name/account
  • Builds a signed request with the admin token
  • POSTs to the Lambda function URL endpoint

This approach avoids direct database access from GAS and keeps all state changes within the AWS Lambda layer.

Infrastructure and Domain Separation

Domain Strategy: All property-specific infrastructure uses dangerouscentaur.com. The tenant hub domain 3028fiftyfirststreet.92105.dangerouscentaur.com is a subdomain following a pattern: [address].[zipcode].dangerouscentaur.com. This allows multiple properties to scale under one domain without cross-contamination.

Email Setup: ImprovMX aliases forward payments@dangerouscentaur.com to the GAS inbox. AWS SES is configured with dangerouscentaur.com as a verified sending domain, with DKIM records added to Namecheap DNS.

S3 and CloudFront: The tenant hub is deployed to a private S3 bucket with CloudFront as the only public access point. The distribution ID is used for cache invalidation on updates.

Key Decisions and Rationale

Why Lambda instead of a traditional server? No persistent server means no maintenance burden, no scaling concerns, and automatic integration with AWS identity and access management. The admin