Building a Payment-to-Portal Pipeline: Email-Driven Zelle Logging for Multi-Tenant Property Management

What Was Done

We implemented a complete payment reconciliation system for a multi-tenant property hub, enabling landlords to forward Zelle payment notifications directly to an automated pipeline that logs rent payments and associates them with specific tenants. The system spans three core components: a tenant credential portal, an email parsing Lambda function, and a Google Apps Script handler that bridges incoming mail to the AWS backend.

The system was intentionally isolated within the dangerouscentaur.com domain (separate from legacy queenofsandiego.com) to maintain clear infrastructure boundaries and avoid cross-domain security/deliverability issues.

Technical Details

Tenant Hub Portal Redesign

The tenant portal lives at https://3028fiftyfirststreet.92105.dangerouscentaur.com/ (deployed via S3 + CloudFront). We updated /index.html to:

  • Generate cryptographically secure temporary passwords for each tenant
  • Display credentials in a protected section requiring admin authentication
  • Add a "Receipts" dashboard section that loads payment history from a backend API
  • Implement client-side credential display with copy-to-clipboard functionality

The hub now serves as the single source of truth for tenant-facing information and payment records within the property domain.

Email-to-Payment Pipeline Architecture

The payment logging system uses a three-stage architecture:

  1. Email Reception: Zelle notifications are forwarded to a dedicated email address provisioned via ImprovMX (email alias forwarding service)
  2. Lambda Email Parser: A new Lambda function at /scripts/lambda-email-parser/lambda_function.py receives the email via SES rule trigger, extracts payment details (amount, sender, timestamp), and writes to a receipts store
  3. Hub Receipt Display: The tenant portal reads from receipts.json (stored in S3) to display payment confirmation

Why this architecture: Using Lambda + SES keeps the system serverless and eliminates the need for a dedicated mail server. ImprovMX provides reliable alias forwarding without exposing the actual bank account email. The JSON store enables version control and simple auditing.

Receipt Action Lambda Enhancement

We extended the existing receipt-action Lambda (deployed at /scripts/lambda-receipt-action/lambda_function.py) to include a new admin action: log_payment.

The function signature accepts:

{
  "action": "log_payment",
  "tenant_id": "3028",
  "amount": 1500.00,
  "payment_method": "zelle",
  "reference": "Bank reference or Zelle transaction ID",
  "admin_token": "ADMIN_TOKEN_VALUE"
}

The handler validates the admin token, appends a new entry to receipts.json with timestamp and tenant association, and invalidates the CloudFront cache to ensure the hub displays updated payment data immediately.

Authentication: An ADMIN_TOKEN environment variable (stored securely in Lambda configuration, not in code) gates access to the log_payment action, preventing unauthorized payment entries.

Google Apps Script Bridge

Updated WarmLeadResponder.gs (the Apps Script project bound to the queenofsandiego.com Google Workspace account) to include a new handler function:

function handleZelleForward(message) {
  // Parse forwarded Zelle email for amount and sender
  // Extract tenant ID from email subject or body
  // Call receipt-action Lambda log_payment action
  // Log success/failure in Apps Script logs
}

When the designated forwarding email receives a Zelle notification, Apps Script:

  • Triggers on incoming message (via Gmail integration)
  • Parses the email body to extract transaction amount and confirmation details
  • Matches the sender phone/account to a known tenant (by cross-referencing the project metadata)
  • Invokes the Lambda endpoint with the payment details and admin token
  • Stores confirmation in a "Payments Logged" label for audit trail

Why Apps Script: It provides a low-friction bridge between Gmail and AWS Lambda without requiring additional infrastructure. The Apps Script project runs as a service account within Google Workspace, giving it reliable access to incoming mail and outbound HTTP calls.

Infrastructure Changes

SES Configuration (dangerouscentaur.com)

Provisioned a verified sender identity for the dangerouscentaur.com domain in AWS SES (us-west-2 region). Added DKIM tokens to the domain's DNS records via Route53:

aws ses verify-domain-identity --domain dangerouscentaur.com --region us-west-2
aws ses verify-domain-dkim --domain dangerouscentaur.com --region us-west-2

Created a receipt-specific email alias (receipts@dangerouscentaur.com) via ImprovMX, forwarded to Apps Script's trigger email address.

Lambda Functions

Email Parser Lambda:

  • Runtime: Python 3.11
  • Path: /scripts/lambda-email-parser/lambda_function.py
  • Trigger: SES rule (receives email as raw message)
  • Permissions: S3 read/write on receipts bucket, CloudFront invalidation
  • Environment: RECEIPTS_BUCKET, RECEIPT_KEY

Receipt Action Lambda:

  • Function URL enabled (for Apps Script HTTP calls)
  • Updated environment variables: ADMIN_TOKEN (set post-deployment, not in code)
  • CloudFront distribution ID for tenant hub cache invalidation

S3 & CloudFront

Tenant portal hosted via S3 bucket dangerouscentaur-tenant-portal (or similar) with CloudFront distribution fronting it. After each portal update:

aws s3 cp index.html s3://dangerouscentaur-tenant-portal/
aws cloudfront create-invalidation --distribution-id DIST_ID --paths "/*"

This ensures both credential updates and payment receipt changes propagate to clients within seconds.

Key Decisions

Domain Isolation

The original credential emails came from queenofsandiego.com. This was corrected to use dangerouscentaur.com exclusively because:

  • Multi-tenant properties need their own identity to avoid confusion with other business units
  • SES domain reputation is separate per domain; mixing sends can harm deliverability
  • Infrastructure-as-code is cleaner when each property has its own resource namespace