Rex Automaton
All posts
Operations & Admin AutomationAugust 8, 202610 min read

AppFolio Workflow Integration: Automate Maintenance, Owner Comms, and AR

A practical, first-hand guide to automating AppFolio workflows: maintenance routing, owner communications, and delinquency follow-up using AppFolio Stack APIs where available and robust CSV email-ingest fallbacks.

By Jacky Lei

AppFolio workflow integration is the practice of connecting AppFolio data to external tools so recurring work runs itself: maintenance tickets route to the right vendor, owners receive timely communications, and delinquent accounts trigger dunning sequences without manual exports.

We built these patterns in production for property managers who wanted maintenance routing, owner updates, and AR follow-ups to run on schedule. This guide shows exactly how we wire it: where to use AppFolio Stack API objects, and where a scheduled CSV export with a parser inbox is the safer, faster path.

The problem it solves

Most teams copy data out of AppFolio to make other tools work. A coordinator exports a report, cleans a CSV, pastes data into email or a CRM, and hopes nothing changed since the last export. Work breaks on vacations, columns drift, and nobody remembers the one VLOOKUP the last analyst added to make the file import.

Manual workflowAutomated workflow
Pull Work Orders report weekly, sort by property, email vendors one by oneScheduled CSV export lands in a parser inbox. The integration engine reads it, matches vendors, and creates tickets automatically
Assemble owner updates from multiple reports and mail-merge in OutlookOwners are read from an API or CSV and get templated, portfolio-specific updates on a schedule
Review Delinquency report daily, paste into SMS tool, send remindersDelinquent Charges are polled via API on Plus or Max, or ingested from a scheduled CSV. Dunning emails and SMS fire with guardrails

The change is predictable processing on a clock. Less keystroking, fewer missed follow-ups, and cleaner handoffs to vendors and CRMs.

How the automation works

At a high level we use two integration surfaces: AppFolio Stack API objects when your plan permits, and scheduled CSV email exports as the universal fallback. A small orchestration service normalizes the inputs and fans them out to maintenance dispatch, owner communications, or AR reminders.

  • AppFolio Stack API where available: AppFolio publishes an API surface via AppFolio Stack that includes objects like Delinquent Charges, Vendors, Properties, and Tenants. Access is plan-gated and partner onboarded. On Plus you get read-only API. On Max you get read and write. We use this for delinquency polling and fresh entity reads when you have access.
  • CSV email-ingest fallback: AppFolio portals can export reports to CSV or Excel. Teams commonly schedule emailed CSVs to a parser inbox as a simple integration path. We harden that path with idempotency keys and schema drift checks.
  • Routing engine: A small worker normalizes fields, applies routing rules, and pushes to downstream systems: ticketing or vendor mailboxes for maintenance, your CRM or email platform for owners, and your email or SMS provider for AR.
  • Idempotency and audit: We compute a natural key per record and store a ledger so replays do not create duplicates. Every send logs the source row and the downstream action.

AppFolio workflow integration: AppFolio Stack API or scheduled CSV export into an orchestration engine, which branches to maintenance routing, owner communications, and delinquency follow-up

Step-by-step: how to build it

Step 1: Choose your integration surface and confirm plan access

Decide per workflow whether to use the API or CSV. AppFolio lists "AppFolio API read only" on Plus and "AppFolio API read/write" on Max. If you do not have API access or partner onboarding yet, schedule the report to email and parse that reliably.

# Decision checklist
- Maintenance routing: start with CSV export of Work Orders. API later for Vendors and Properties
- Owner comms: start with Owner or Portfolio CSV exports for mail-merge
- Delinquency: prefer API polling of Delinquent Charges on Plus or Max. Otherwise schedule a daily CSV

Key gotcha: public developer pages do not disclose base URL or auth details. Treat API credentials and endpoints as provided during partner onboarding. Build your code to read API_URL and API_TOKEN from env vars so you can swap surfaces.

Step 2: Set up a parser inbox and save attachments safely

Create a dedicated inbox for scheduled CSVs. Forward only the AppFolio report emails into a label and let a small Apps Script or IMAP job pull attachments and store them in cloud storage for the worker.

// Apps Script: pull new CSVs from a Gmail label and upload to Drive
function ingestScheduledCSVs() {
  const label = GmailApp.getUserLabelByName('appfolio/scheduled');
  const threads = label.getThreads(0, 50);
  threads.forEach(t => {
    t.getMessages().forEach(m => {
      m.getAttachments({includeInlineImages: false, includeAttachments: true})
       .filter(a => /\.csv$/i.test(a.getName()))
       .forEach(a => {
         const blob = a.copyBlob();
         const file = DriveApp.getFolderById(PropertiesService.getScriptProperties().getProperty('CSV_FOLDER_ID'))
           .createFile(blob).setName(`${Date.now()}_${a.getName()}`);
         // Write a small sidecar JSON to signal the worker
         // contents: { fileId, filename, receivedAt }
       });
    });
    // Optional: remove label to avoid reprocessing
    t.removeLabel(label);
  });
}

Gotcha: AppFolio report formats can change. Snapshot the first header row per report and alert on drift before processing.

Step 3: Transform maintenance CSVs and route to the right vendor

Parse the Work Orders export, compute a deterministic key, match vendors by property or category, then create tickets or vendor emails. Keep the vendor map in a table so operations can change routing without code.

// Node.js: parse CSV and fan out to vendors
import { parse } from 'csv-parse/sync';
import axios from 'axios';
 
export async function handleWorkOrders(csvBuf: Buffer) {
  const rows = parse(csvBuf, { columns: true, skip_empty_lines: true });
  for (const r of rows) {
    const key = `${r["Work Order ID"]}:${r["Reported Date"]}`; // idempotency key
    if (await seen(key)) continue;
 
    const vendor = await pickVendor({
      property: r["Property"],
      category: r["Category"],
      priority: r["Priority"],
    });
 
    if (vendor.type === 'ticketing') {
      await axios.post(process.env.TICKETING_URL as string, {
        title: `[${r["Property"]}] ${r["Category"]}: ${r["Problem Description"]}`,
        details: r["Detailed Description"],
        vendorId: vendor.id,
        attachments: [],
      }, { headers: { Authorization: `Bearer ${process.env.TICKETING_TOKEN}` }});
    } else {
      await sendVendorEmail(vendor.email, r);
    }
 
    await markSeen(key);
  }
}

Gotcha: new categories appear over time. Add a default routing rule with a human approval queue so unknown categories do not silently drop.

Step 4: Poll delinquent charges via API or ingest the daily CSV

If your plan includes API access, read the Delinquent Charges object on a schedule and trigger dunning sequences. Otherwise, use the CSV path. Store an as_of date and the charge identifier as a composite key so reminders never double-send.

// Generic API poller: env-configured URL and token from partner onboarding
import axios from 'axios';
 
export async function pollDelinquentCharges() {
  const url = `${process.env.APPFOLIO_API_URL}/delinquent_charges`; // endpoint shape is illustrative
  const resp = await axios.get(url, {
    headers: { Authorization: `Bearer ${process.env.APPFOLIO_API_TOKEN}` }
  });
  for (const c of resp.data.items ?? []) {
    const key = `${c.charge_id}:${c.as_of}`;
    if (await seen(key)) continue;
    await queueDunning(c);
    await markSeen(key);
  }
}

Gotcha: do not assume public base URLs or auth mechanics. Keep these in configuration supplied during AppFolio Stack onboarding. If you are on CSV, parse the daily Delinquency export with the same idempotency scheme.

Step 5: Generate owner communications from exports and send

Use a simple document template with token replacement for each owner or portfolio. We keep the template in Drive and fill tokens from the Owner or Portfolio CSV, then send through your email provider.

// Apps Script: simple owner mail-merge from a template
function sendOwnerUpdate(row) {
  const tmplId = PropertiesService.getScriptProperties().getProperty('OWNER_TEMPLATE_ID');
  const file = DriveApp.getFileById(tmplId).makeCopy(`Owner Update - ${row.OwnerName}`);
  const doc = DocumentApp.openById(file.getId());
  let body = doc.getBody().getText();
  body = body.replaceAll('{{OWNER_NAME}}', row.OwnerName)
             .replaceAll('{{PORTFOLIO_NAME}}', row.Portfolio)
             .replaceAll('{{UNITS}}', String(row.UnitCount))
             .replaceAll('{{HIGHLIGHTS}}', row.Highlights || '');
  doc.getBody().setText(body);
  doc.saveAndClose();
  GmailApp.sendEmail(row.Email, `Update for ${row.Portfolio}`, body, { name: 'Asset Management' });
}

Gotcha: send owner communications from an appropriate mailbox and brand. If legal requires opt-outs for broad notices, send via your marketing platform instead of raw Gmail.

Step 6: Add a ledger for idempotency and monitoring

Whether you run API or CSV, store a row per action you took. This single table prevents duplicates and powers dashboards and alerts.

-- Postgres ledger for cross-workflow idempotency and audit
create table if not exists integration_ledger (
  key text primary key,
  workflow text not null,      -- maintenance, owner_comms, delinquency
  source text not null,        -- api, csv
  payload jsonb not null,
  delivered_at timestamptz not null default now(),
  downstream_ref text          -- ticket id, email id, provider message id
);

Gotcha: do not rely on filenames or email timestamps as identity. Always compute a key from business fields like Work Order ID plus date, or Charge ID plus as_of.

Where it gets complicated

  • Plan-gated API access: AppFolio's pricing page shows API access is read only on Plus and read/write on Max. Expect partner onboarding. Because public developer docs do not disclose base URL or auth, build your code to treat these as injected configuration.
  • No official Zapier app: Zapier lists 6,000 plus apps in its directory, but there is no official AppFolio connector. The pattern we use is Webhooks by Zapier on the downstream side or a standalone worker that ingests emailed CSVs and pushes to your tools. Source: Zapier Apps directory and Zapier community guidance.
  • Schema drift in CSVs: Columns can appear, rename, or reorder without notice. Protect your parser with header-name mapping, not column indexes. Alert when an expected header is missing before you process rows.
  • Email deliverability to parser inbox: If your scheduled report emails start landing in spam, the pipeline halts. Give the parser inbox a first-class mailbox, add sender allow-lists, and avoid auto-forward chains that rewrite headers.
  • Idempotency across multiple sources: When maintenance or AR data can arrive via both API and CSV during cutover, key collisions can happen. Namespacing the source inside your composite key avoids duplicates and makes replay safe.

What this actually changes

In production we replaced manual report pulls and one-off emails with a scheduled engine that runs every day without supervision. Maintenance tickets are routed as soon as the CSV lands. Owners receive a templated, portfolio-specific communication on a set cadence. Delinquency follow-ups start on time, every time, with guardrails against duplicates.

One useful market reality: Zapier lists 6,000 plus apps in its directory, but there is no official AppFolio connector, which is why reliable integrations lean on Webhooks by Zapier or email-ingested CSV plus a small worker. Source: https://zapier.com/apps and Zapier community guidance at https://community.zapier.com/how-do-i-3/integrating-appfolio-with-gohighlevel-for-property-management-communication-52323.

Frequently asked questions

Does AppFolio have an official API?

Yes. AppFolio exposes an API via AppFolio Stack with objects like Delinquent Charges, Vendors, Properties, and Tenants. Access is plan-gated and requires partner onboarding. Plus provides read-only access. Max provides read and write.

Is there a native Zapier or Make.com connector for AppFolio?

There is no official AppFolio app in Zapier's directory. The common approach is to use Webhooks by Zapier to call your integration or to ingest scheduled CSV exports and push data to your downstream tools. Treat Make similarly unless your account has a private connector.

Can this run in real time?

When an official webhook is not available, near real time means polling the API on a short cadence or scheduling emailed CSV exports frequently. We design around idempotency so tighter schedules do not create duplicates if the same record appears twice.

Which AppFolio plan do I need?

If you want to read entities or reports via the API, AppFolio lists API read only on Plus and read/write on Max. If you are on a lower tier or waiting on partner onboarding, the CSV email-ingest path works today and can be swapped to API later.

How long does a typical integration take to go live?

Maintenance routing and delinquency follow-up often start with the CSV path and can go live quickly once you schedule the reports. API-backed reads require access and credentials. We design both paths so they are swappable without a rebuild.

Can a non-developer set this up?

You can schedule exports and forward them to a parser inbox without code. The reliable parts, like schema drift handling, idempotency, and downstream API pushes, benefit from an engineer so the system survives drift and resend scenarios.

If you want maintenance, owner communications, and AR running on a clock instead of on someone's to-do list, we have shipped this exact pattern. See our AppFolio reporting post for context in the same ecosystem: How to Automate AppFolio Investor Reporting. If you prefer to hand it off end-to-end, review our workflow automation services and book a 15-minute call.

Want us to build this for you?

15-minute discovery call. No pitch. We tell you what to automate first.

Book a Discovery Call

Related reading