Building a Tenant Payment Logging System: Zelle-to-Lambda Email Pipeline with SES Domain Separation
Overview
We implemented a complete payment logging system for a multi-tenant property management portal, with a critical requirement: strict domain isolation between the property management business (dangerouscentaur.com) and the real estate agent operations (queenofsandiego.com). The system allows tenants to forward Zelle payment confirmations to an automated email handler that logs deposits directly to the tenant hub, eliminating manual accounting overhead.
What Was Done
- Provisioned a new SES sender identity on
dangerouscentaur.comwith proper DKIM verification - Generated fresh temporary credentials for tenants and deployed updated portal at
https://3028fiftyfirststreet.92105.dangerouscentaur.com/ - Sent credential emails via SES from
dangerouscentaur.comdomain (not the previousqueenofsandiego.com) - Extended the Lambda receipt-action function with a new
log_paymentadmin action - Wired Google Apps Script (GAS) command handler to detect forwarded Zelle emails and trigger Lambda payment logging
- Configured ImprovMX email aliasing for the property portal inbox
Technical Architecture
Frontend: Tenant Hub Portal
The tenant hub is a single-page application hosted at:
/Users/cb/Documents/repos/sites/dangerouscentaur/demos/3028fiftyfirststreet.92105.dangerouscentaur.com/index.html
Updated credentials are embedded directly in the HTML as a JSON data structure (credentials table), accessed by dashboard initialization code. The portal is deployed to S3 and served through CloudFront with cache invalidation to ensure tenants immediately receive their login credentials.
Backend: Lambda Functions
Two Lambda functions handle the payment flow:
- Receipt-Action Lambda: Located at
/scripts/lambda-receipt-action/lambda_function.py, now extended with anadmin_log_paymentaction that validates an admin token and writes payment entries toreceipts.jsonin S3 - Email-Parser Lambda: Located at
/scripts/lambda-email-parser/lambda_function.py, parses incoming emails forwarded to the property management inbox
Both Lambdas are deployed with Function URLs, enabling direct HTTP invocation. The receipt-action Lambda requires an ADMIN_TOKEN environment variable for payment logging, preventing unauthorized deposits from being recorded.
Email Pipeline: SES + GAS + ImprovMX
The email workflow consists of three stages:
- Tenant sends Zelle confirmation to a
dangerouscentaur.comemail alias (e.g.,payments@dangerouscentaur.com) - ImprovMX forwards the email to a Google Sheets webhook endpoint managed by Google Apps Script
- GAS command handler (in
WarmLeadResponder.gs) detects the Zelle payment pattern, extracts amount/date, and calls the Lambda receipt-action function with the admin token - Lambda logs the payment to
receipts.jsonin the property portal S3 bucket
Infrastructure and Configuration
SES Domain Setup
We initiated domain verification for dangerouscentaur.com in AWS SES with DKIM signing enabled. This required:
- Retrieving three DKIM tokens from the SES console
- Adding CNAME records to the domain's DNS provider (Namecheap) to complete DKIM verification
- Verifying the sender identity before production email delivery
This ensures all outbound tenant credentials and system emails originate from dangerouscentaur.com`, not the real estate agent's domain, maintaining clear business separation.
S3 and CloudFront
The tenant portal is stored in an S3 bucket and distributed through CloudFront. When credentials are updated:
- Updated
index.htmlis uploaded to S3 - CloudFront cache is invalidated (full invalidation with
/*pattern) - Tenants receive fresh credentials within seconds
Lambda Environment Variables
The receipt-action Lambda requires:
ADMIN_TOKEN: A strong, randomly-generated token used to authenticate payment logging requests from GAS- S3 bucket and credentials path for reading/writing
receipts.json
This token is stored in repos.env in the local development environment and added to the Lambda function configuration via AWS CLI:
aws lambda update-function-configuration \
--function-name dangerouscentaur-receipt-action \
--environment Variables={ADMIN_TOKEN=<token>}
ImprovMX Alias Configuration
ImprovMX provides email aliasing without running a mail server. The setup:
- Create alias:
payments@dangerouscentaur.com - Forward to: GAS webhook endpoint (or internal email for manual review during testing)
- Enable verification to prevent spoofing
This allows the property manager to accept tenant Zelle forwards without exposing personal email addresses.
Key Technical Decisions
Why Separate Domains?
Mixing queenofsandiego.com (real estate agent) with dangerouscentaur.com (property management) creates compliance and branding issues. Tenants should interact exclusively with the property management entity. SES sender verification ensures email deliverability and domain reputation are independent.
Why GAS for Email Command Parsing?
Google Apps Script provides free, serverless email webhook handling integrated with Google Sheets. It's ideal for pattern matching (detecting Zelle emails) and calling Lambda functions without adding infrastructure complexity. The existing WarmLeadResponder.gs command handler already processes structured emails; we extended it to handle payment forwarding.
Why Admin Token on Lambda?
Payment logging modifies the financial record. An admin token prevents a compromised tenant account or misconfigured email rule from adding false deposits. The token is passed in the HTTP request body and validated server-side before writing to receipts.json`.
Why S3 + JSON for Receipts?
The tenant hub reads receipts.json on page load to display payment history. Storing receipts as JSON in S3 keeps the system simple and queryable without a database, and CloudFront caching ensures fast reads. Writes are append-only and atomic within Lambda.
Deployment Steps (Examples)
Generate and deploy new credentials: