Build documentation

IntakeSync runbook

JetFormBuilder intake to Airtable client database, with deduplication and error handling. Everything you need to import the blueprint, point it at your own base, and run it.

← back to the walkthrough

What it does

A WordPress JetFormBuilder form posts each submission to a Make webhook. The scenario validates the email, searches your Airtable Clients table for that person, and then either creates a new record or updates the existing one. A person who submits more than once updates their row rather than creating a duplicate.

Six modules: a webhook trigger, a validate and normalize step, an Airtable search, and a router into an Airtable create and an Airtable update. Each write carries an error handler.

Before you import

You need one connection and two ids.

WhatWhere it goes
Airtable connectionSet on the Search, Create, and Update modules. A personal access token with data.records:read and data.records:write on the base is enough.
Base id (app...)The base field on the three Airtable modules.
Table id or nameThe table field on the three Airtable modules. The blueprint uses a placeholder Clients table.

The blueprint writes fields by name (useColumnId is false), so the field names below need to exist in your table. Rename them to whatever your base already uses and adjust the mapping to match.

Set up the JetFormBuilder side

In the form's Post Submit Actions, add or switch to Call Webhook. Paste the webhook address Make gives you when you open the webhook trigger. Submit the form once with real looking values. In Make, the trigger shows Successfully determined, which means it has mapped each field to a top level key.

Run Determine Data Structure only once, from a submission that has every field filled, so optional fields are included in the map. After that the field names are fixed and the downstream modules resolve cleanly.

Module by module

#ModuleDoesKey settings
1 gateway:CustomWebHook Receives the JetFormBuilder submission. Attach the hook, then set the JetFormBuilder Call Webhook to this address.
2 util:SetVariables Validates the email and sets a normalized key. Filter requires email present and well formed. Sets email_norm = lower(trim(1.email)).
3 airtable:Search Records Finds an existing client by email. Formula AND({Email}!="", LOWER({Email})=LOWER("{{2.email_norm}}")), max records 1. Count read as {{3.__IMTLENGTH__}}.
4 builtin:BasicRouter Splits into create vs update. Two routes with mutually exclusive filters on the search count.
5 airtable:Create a Record Adds a new client (new person only). Filter {{3.__IMTLENGTH__}} = 0. Stamps Status New, First Seen now, Intake Count 1. typecast on.
6 airtable:Update a Record Refreshes the existing client (returning only). Filter {{3.__IMTLENGTH__}} > 0. Record id {{3.id}}. Bumps Intake Count, leaves First Seen and Email alone.

Field mapping

Form field on the left, Clients table field on the right. This is the mapping used on both the create and update writes. Adjust names to your base.

JetFormBuilder fieldAirtable fieldNotes
full_nameNameSingle line text.
emailEmailWritten lowercased on create. The match key. Not overwritten on update.
phonePhoneSingle line text.
ageAgeNumber. typecast coerces the text the form sends.
cityCitySingle line text.
seekingSeekingSingle select or text.
age_range_preferencePreferred Age RangeSingle line text.
how_heardSourceSingle select or text.
consentConsentCheckbox, coerced from the string true.
set by scenarioStatusNew on create.
set by scenarioFirst SeenStamped on create only, preserved on update.
set by scenarioLast UpdatedStamped on every write.
set by scenarioIntake Count1 on create, plus one on each update.

The two parts worth getting right

1. Field mapping stability

Run Determine Data Structure once from a fully filled submission. If you map from a submission that skipped optional fields, those fields are missing from the structure and later resolve as empty. Fill everything on the mapping run.

2. Deduplication

Airtable does not enforce a unique field, so the automation is what keeps one row per person. Two things make the match reliable: the email is normalized (lowercased and trimmed) on both sides, and the search formula compares LOWER to LOWER so casing never causes a false new record. If your base already contains duplicates, the search returns the first match; decide the tie break deliberately, for example update the oldest record and add a step that flags any email with more than one row for a cleanup pass.

Error handling

SituationWhat happens
Blank or malformed emailThe validation filter stops the run before Airtable. No junk record is written.
Airtable rate limit or 5xx on a writeThe onerror handler retries 3 times at 5 minute intervals.
Still failing after retriesbuiltin:Break stores the run as an incomplete execution to resume, so the submission is not lost.
One bad submission in a burstThe scenario error tolerance keeps the rest running.
Want a louder signalAdd a notify step (email or Slack) before the Break, or a dead letter table that stores the raw payload for review.

A more compact alternative

If you would rather not branch, Airtable has a single Upsert a Record module with a fields to merge on setting. Set it to merge on Email and it does the search, create, and update in one module. It is tidier for a straight one to one sync. The reason this build uses the explicit search then router is control: you can log which path ran, apply different logic on update than on create, and handle the already has duplicates case on purpose rather than hoping the merge picks the right row. Either is correct; pick the one that fits how much you want to see.

Import steps

1. In Make, create a scenario, open the menu, and choose Import Blueprint. Select intake-sync.blueprint.json.
2. Open the Airtable modules and set the connection, then the base and table.
3. Open the webhook trigger, copy its address, and paste it into the JetFormBuilder Call Webhook action.
4. Submit the form once and run Determine Data Structure.
5. Check the field mapping on the create and update modules against your table's field names.
6. Turn the scenario on and submit a real test, once as a new email and once as the same email again, to see the create then the update.

← back to the walkthrough