English
Airtable
Finds, creates, completes and comments rows in an Airtable base, and drops the attachments (from the email or added by a step) into it.
The Airtable node finds, creates, completes and comments rows in an Airtable base, and drops files into an attachment field. Use it to keep a case register in step with your mailbox: one row per request, completed as emails arrive, without duplicates.
It works with a personal access token stored once in Connections. See Airtable to create the connection and choose the scopes. To start a workflow when a row changes, use the Airtable row changed trigger. For data that only lives inside Mankomail, the built-in tables are simpler.
The base, the table, the view and the fields are chosen from lists loaded from your Airtable account. The workflow stores their ids (app…, tbl…, fld…, viw…), so renaming a table or a column in Airtable does not break it. Changing the base empties the table, and changing the table empties the fields that depend on it.
At a glance
- Type:
airtable.api· version 1 - Category: Data
- Kind: Step — one stage of a run
- Effect: Writes outside (
external_write) — writes outside Mankomail; described instead of performed during a test run - Needs a carrier email: No
- Connection: Airtable
- Inputs:
main - Outputs:
main
Connection
This node needs a Airtable connection.
Parameters
connection
Airtable connection — The Airtable connection to use. Create it once in Connections; its key never appears in the workflow.
- Type: Connection (
credential) - Required: Yes
- Default:
""(empty)
resource
Resource
- Type: One choice (
options) - Required: Yes
- Default:
record - Options:
record— Record: A row in a table: find it, read it, create it, complete it, comment it.attachment— Attachment: Drop an email attachment into an attachment field, or list the ones on a record.base— Base: The accessible bases, and one base’s schema (tables, fields, views).
recordOperation
Operation
- Type: One choice (
options) - Required: Yes
- Default:
search - Options:
search— Search: The rows matching a filter, a view, a sort.get— Get a record: By itsrec…id. Not found is not an error:foundis false.create— Create: Adds a row. ⚠️ A retry of the step adds a second one: prefer “Create or update”.upsert— Create or update: Matches on a key field: updates the row if it exists, creates it otherwise. The clean way to make a workflow safe to retry.update— Update: Changes a row known by its id.delete— Delete: Sends the row to the base trash.comment— Comment: Adds a comment on the row.@[usrXXXX]mentions a collaborator.
- Shown when:
resourceisrecord
attachmentOperation
Operation
- Type: One choice (
options) - Required: Yes
- Default:
upload - Options:
upload— Upload the attachments: Those of the email and the files added by an earlier step. The bytes go straight to Airtable: the document is never exposed on a public URL.list— List a record’s attachments: Name, size, type and download link. ⚠️ Links expire after two hours.download— Fetch a record’s attachments into the run: Fetches the files into the run’s attachments: a “Compose an email” can then attach them by their position.
- Shown when:
resourceisattachment
maxDownloadMb
Maximum size per file (MB)
- Type: Number (
number) - Required: No
- Default:
10 - Whole number, from 1 to 10
- Shown when:
resourceisattachmentandattachmentOperationisdownload
baseOperation
Operation
- Type: One choice (
options) - Required: Yes
- Default:
schema - Options:
schema— Get a base schema: Its tables, their fields and views, with thetbl…,fld…,viw…ids.list— List the bases: The ones the token can reach, with their permission level.
- Shown when:
resourceisbase
base
Base — The Airtable base. The “URL” mode accepts an airtable.com/appXXXX/… address.
- Type: Remote resource (
resourceLocator) - Required: Yes
- Ways to choose: pick from a list, type an ID, paste a URL (
airtable.base) - Listed with the connection in:
connection - Shown when:
resourceisrecordorresourceisattachmentor (resourceisbaseandbaseOperationisschema)
table
Table
- Type: Remote resource (
resourceLocator) - Required: Yes
- Ways to choose: pick from a list, type an ID, paste a URL (
airtable.table) - Listed inside:
base - Listed with the connection in:
connection - Shown when:
resourceisrecordorresourceisattachment
view
View — Optional: restricts the search to the rows visible in that view. ⚠️ Hidden fields are still returned.
- Type: Remote resource (
resourceLocator) - Required: No
- Ways to choose: pick from a list, type an ID, paste a URL (
airtable.view) - Listed inside:
table - Listed with the connection in:
connection - Shown when:
resourceisrecordandrecordOperationissearch
filterMode
Filter
- Type: One choice (
options) - Required: No
- Default:
conditions - Options:
conditions— Conditions: Assembled into an Airtable formula, properly escaped.formula— Airtable formula: For what conditions cannot express. Escaping values is then up to you.none— None: Every row (of the chosen view, if any).
- Shown when:
resourceisrecordandrecordOperationissearch
conditions
Conditions — A field is named by its name or its fld… id. Values are escaped: an email subject cannot turn into a formula.
- Type: List of items (
collection) - Required: No
- At most 10 items
- Each item has:
field— Field- Type: Text (
string) - Required: No
- Default:
""(empty) - 200 characters at most
- Example:
Case reference
- Type: Text (
operator— Operator- Type: One choice (
options) - Required: No
- Default:
equals - Options:
equals— equalsnotEquals— does not equalcontains— containsisEmpty— is emptyisNotEmpty— is not emptygreaterThan— is greater thanlessThan— is less thanbefore— is before the dateafter— is after the dateolderThanDays— is older than (days)newerThanDays— is newer than (days)
- Type: One choice (
value— Value. Ignored by “is empty” and “is not empty”. A date reads2026-09-22.- Type: Text (
string) - Required: No
- Default:
""(empty) - 500 characters at most
- Type: Text (
- Shown when:
resourceisrecordandrecordOperationissearchandfilterModeisconditions
filterCombine
Combination
- Type: One choice (
options) - Required: No
- Default:
all - Options:
all— All conditionsany— At least one condition
- Shown when:
resourceisrecordandrecordOperationissearchandfilterModeisconditions
formula
Airtable formula — filterByFormula, as Airtable writes it. Escape values coming from an email yourself.
- Type: Long text (
text) - Required: No
- Default:
""(empty) - 8000 characters at most
- Example:
{Reference} = "2026-0412" - Shown when:
resourceisrecordandrecordOperationissearchandfilterModeisformula - Expressions:
{{ }}accepted
sortField
Sort by — Optional. With no sort and no view, the order Airtable returns rows in is arbitrary.
- Type: Remote resource (
resourceLocator) - Required: No
- Ways to choose: pick from a list, type an ID (
airtable.field) - Listed inside:
table - Listed with the connection in:
connection - Shown when:
resourceisrecordandrecordOperationissearch
sortDirection
Sort direction
- Type: One choice (
options) - Required: No
- Default:
asc - Options:
asc— Ascendingdesc— Descending
- Shown when:
resourceisrecordandrecordOperationissearch
fields
Fields to return — Names or ids, comma separated. Empty = all of them. Naming three instead of forty keeps the output and the expressions reading it small.
- Type: Text (
string) - Required: No
- Default:
""(empty) - 2000 characters at most
- Shown when:
resourceisrecordandrecordOperationis one ofsearch,get - Expressions:
{{ }}accepted
limit
Maximum count
- Type: Number (
number) - Required: No
- Default:
50 - Whole number, from 1 to 1000
- Shown when:
resourceisrecordandrecordOperationissearch
recordId
Record — The rec… id, as a search returns it in data. Several comma-separated ids are accepted for delete and update.
- Type: Text (
string) - Required: No
- Default:
""(empty) - 2000 characters at most
- Example:
{{ data.api_1.recordId }} - Shown when: (
resourceisrecordandrecordOperationis one ofget,update,delete,comment) orresourceisattachment - Expressions:
{{ }}accepted
values
Fields — The field name (or fld… id) and its value. Values accept {{ }} expressions: {{ email.subject }}.
- Type: Key / value pairs (
keyValue) - Required: No
- Shown when:
resourceisrecordandrecordOperationis one ofcreate,update,upsert - Expressions:
{{ }}accepted
fieldsJson
Fields as JSON (advanced) — For values that are not text: numbers, checkboxes, lists, links to other tables. An object = one record; an array of objects = several, sent in batches of 10. Merged over the fields above.
- Type: Long text (
text) - Required: No
- Default:
""(empty) - 20000 characters at most
- Example:
{ "Amount": 1200, "Urgent": true, "Case": ["rec123"] } - Shown when:
resourceisrecordandrecordOperationis one ofcreate,update,upsert - Expressions:
{{ }}accepted
mergeField
Merge field — The business key: case reference, email address, Message-ID. ⚠️ If several rows share the value, Airtable rejects the request — the key must be unique.
- Type: Remote resource (
resourceLocator) - Required: Yes
- Ways to choose: pick from a list, type an ID (
airtable.mergeField) - Listed inside:
table - Listed with the connection in:
connection - Shown when:
resourceisrecordandrecordOperationisupsert
extraMergeFields
Additional merge fields — Optional, two at most (Airtable accepts three in total), comma separated.
- Type: Text (
string) - Required: No
- Default:
""(empty) - 400 characters at most
- Shown when:
resourceisrecordandrecordOperationisupsert - Expressions:
{{ }}accepted
typecast
Automatically convert values — ⚠️ Airtable then creates missing select options and missing linked records. Needed to map an AI output onto a select, risky on a well-kept base: a typo becomes a new option.
- Type: Yes / no (
boolean) - Required: No
- Default:
false - Shown when:
resourceisrecordandrecordOperationis one ofcreate,update,upsert
clearUnspecified
Clear unspecified fields — ⚠️ Replaces the whole record (PUT): every field not listed above is cleared. Unchecked, only the given fields change (PATCH).
- Type: Yes / no (
boolean) - Required: No
- Default:
false - Shown when:
resourceisrecordandrecordOperationisupdate
commentText
Comment — Accepts {{ }} expressions. @[usrXXXXXXXXXXXXXX] mentions an Airtable collaborator and notifies them.
- Type: Long text (
text) - Required: No
- Default:
""(empty) - 10000 characters at most
- Shown when:
resourceisrecordandrecordOperationiscomment - Expressions:
{{ }}accepted
attachmentField
Attachment field — Only attachment-type fields are offered. Uploading appends: the files already there are kept.
- Type: Remote resource (
resourceLocator) - Required: Yes
- Ways to choose: pick from a list, type an ID (
airtable.attachmentField) - Listed inside:
table - Listed with the connection in:
connection - Shown when:
resourceisattachment
attachmentSelect
Attachments to upload
- Type: One choice (
options) - Required: No
- Default:
all - Options:
all— All of them: Those of the email, then the files added by earlier steps.first— The first one onlypositions— The ones I name: By their position, as “Read attachments” returns it.
- Shown when:
resourceisattachmentandattachmentOperationisupload
attachmentPositions
Positions — Comma separated: 1,3.
- Type: Text (
string) - Required: No
- Default:
""(empty) - 200 characters at most
- Shown when:
resourceisattachmentandattachmentOperationisuploadandattachmentSelectispositions - Expressions:
{{ }}accepted
includeInline
Include inline images — Unchecked, signature logos and other cid: images are skipped — almost always what you want.
- Type: Yes / no (
boolean) - Required: No
- Default:
false - Shown when:
resourceisattachmentandattachmentOperationisupload
Outputs
main— Taken once the Airtable operation has succeeded. An error answer from Airtable fails the step instead (except Get a record and the attachment list or fetch, where a missing record givesfound: false).
Data produced
What this node adds to the run data, and how to read it in an expression. <step> stands for the step key: the node name turned into an identifier (see Data and expressions).
{{ data.<step>.records }}—array of { id, createdTime, fields }. Search, Create, Update, Create or update: the records returned by Airtable.idis therec…id,fieldsthe field values keyed by field name, exactly as Airtable returns them (empty values are omitted by Airtable).{{ data.<step>.recordId }}—string. Therec…id of the first record concerned: the first match (Search), the record read (Get), the first record written (Create, Update, Create or update), or the record targeted (Comment, attachment operations). Empty when there is none.{{ data.<step>.fields }}—object. Search, Get a record, Create or update: the fields of the first record, so that{{ data.<step>.fields.Owner }}works without indexing a list.{{ data.<step>.found }}—boolean. Search: true when at least one row matched. Get a record and the attachment list or fetch: true when the record could be read.{{ data.<step>.count }}—number. The number of items concerned: rows found, records written, records deleted, bases, tables, attachments uploaded, listed or fetched.{{ data.<step>.truncated }}—boolean. Search and List the bases: true when more items existed than the limit allowed.{{ data.<step>.record }}—{ id, createdTime, fields } or null. Get a record only: the record read, null when it was not found.{{ data.<step>.recordIds }}—array of string. Create and Update: the ids of the records written (for Update, the ids targeted).{{ data.<step>.warnings }}—array of string. Create, Update, Create or update: the reasons Airtable gives when it accepted the write only partly (for example attachments it could not fetch). Empty in the normal case.{{ data.<step>.cleared }}—boolean. Update only: true when Clear unspecified fields was on, so that the whole record was replaced.{{ data.<step>.created }}—boolean. Create or update only: true when at least one record was created rather than updated. Always false in a test run.{{ data.<step>.createdIds }}—array of string. Create or update only: the ids of the records created.{{ data.<step>.updatedIds }}—array of string. Create or update only: the ids of the existing records updated.{{ data.<step>.createdCount }}—number. Create or update only: the number of records created.{{ data.<step>.updatedCount }}—number. Create or update only: the number of records updated.{{ data.<step>.mergeFields }}—array of string. Create or update only: the merge fields actually used (1 to 3).{{ data.<step>.deletedIds }}—array of string. Delete only: the ids of the records deleted (in a test run, the ids that would have been deleted).{{ data.<step>.commentId }}—string. Comment only: the id of the comment created, empty in a test run.{{ data.<step>.text }}—string. Comment only: the comment text sent.{{ data.<step>.createdTime }}—string. Comment only: the creation time of the comment, empty in a test run.{{ data.<step>.bases }}—array of { id, name, permissionLevel }. List the bases only: the bases the token can reach.{{ data.<step>.baseId }}—string. Get a base schema only: theapp…id of the base read.{{ data.<step>.tables }}—array of { id, name, primaryFieldId, fields, views }. Get a base schema only: the tables of the base, each withfields(array of{ id, name, type }) andviews(array of{ id, name, type }).{{ data.<step>.field }}—string. Upload the attachments only: the attachment field written to.{{ data.<step>.uploaded }}—array of { filename, size, type, origin, addedByNodeId? }. Upload the attachments only: the files sent.originisemail(an attachment of the triggering email) oradded(a file added by an earlier step, whose node id is inaddedByNodeId).{{ data.<step>.skipped }}—array. Upload the attachments: the files left out because they exceed 5 MB, as{ filename, size }. Fetch a record's attachments: the files left out, as{ field, id, filename, size, type, url, reason }withreasonset totoo_largeortoo_many.{{ data.<step>.skippedCount }}—number. Upload the attachments only: the number of files left out.{{ data.<step>.attachments }}—array of { field, id, filename, size, type, url }. List a record's attachments: the attachments of the record. Fetch a record's attachments: the files fetched, withattachmentPosition(their position in the run's attachments) anddeduplicatedin addition.{{ data.<step>.urlExpiresInMinutes }}—number. List a record's attachments only: how long theurllinks stay valid, always 120.{{ data.<step>.positions }}—array of number. Fetch a record's attachments only: the positions of the fetched files in the run's attachments, ready for a Compose step.{{ data.<step>.simulated }}—boolean. true when a write was described rather than performed (test run). Reads are never simulated.{{ data.<step>.summary }}—string. A one-line summary in the member's language, for example '1 created, 0 updated.' (withsummaryKeyandsummaryParams).
Operations
The node first asks for a Resource (resource), then for the operation of that resource.
Record (resource: record, operation in recordOperation)
- Search (
search, the default). Lists the rows of Table (table) that match the filter, up to Maximum count (limit, 1 to 1,000, default 50). Filter (filterMode) is either Conditions (conditions: up to 10 rows of field, operator, value, combined by CombinationfilterCombine, all or any), an Airtable formula (formula, sent asfilterByFormula), or None. View (view) restricts to the rows visible in that view; Sort by (sortField) and Sort direction (sortDirection) order the result; Fields to return (fields, comma-separated) limits the columns. CallsGET /v0/{base}/{table}, orPOST …/listRecordswith the same parameters when the query becomes long. Publishesrecords,count,found,recordId,fields,truncated. - Get a record (
get). Reads the record whose id is in Record (recordId, the first id if several are given), with Fields to return. An unknown record (Airtable answers 404) is not an error:foundis false andrecordis null. Publishesfound,record,recordId,fields. - Create (
create). Adds rows withPOST /v0/{base}/{table}. Values come from Fields (values, field name orfld…id and value) and Fields as JSON (advanced) (fieldsJson): a JSON object is one record, an array of objects is several records, sent in batches of 10. Automatically convert values (typecast) lets Airtable convert text into select options and linked records. Publishesrecords,recordIds,recordId,count,warnings. - Create or update (
upsert). SendsPATCH /v0/{base}/{table}withperformUpsert: Airtable looks for a row whose Merge field (mergeField, plus up to two Additional merge fieldsextraMergeFields) has the same value, updates it if it exists, creates it otherwise. Same value inputs as Create. Publishesrecords,recordId,fields,createdIds,updatedIds,createdCount,updatedCount,created,mergeFields,warnings. - Update (
update). Changes the records whose ids are in Record (several comma-separated ids accepted), or theidcarried by each object of the JSON. By default only the given fields change (PATCH). With Clear unspecified fields (clearUnspecified), the whole record is replaced (PUT) and every field not given is emptied. Publishesrecords,recordIds,recordId,count,cleared,warnings. - Delete (
delete). Sends the records listed in Record to the base trash (DELETE, batches of 10). PublishesdeletedIds,count. - Comment (
comment). Posts Comment (commentText) on the record in Record.@[usrXXXXXXXXXXXXXX]mentions and notifies a collaborator. PublishescommentId,recordId,text,createdTime.
Attachment (resource: attachment, operation in attachmentOperation)
- Upload the attachments (
upload, the default). Sends files of the run to the Attachment field (attachmentField) of the record in Record. Attachments to upload (attachmentSelect) is All of them (the email's attachments, then the files added by earlier steps), The first one only, or The ones I name (PositionsattachmentPositions, for example1,3). Include inline images (includeInline) is off by default. The bytes go straight tocontent.airtable.com(POST …/uploadAttachment), never through a public URL. Uploading appends: files already in the field are kept. PublishesrecordId,field,uploaded,count,skipped,skippedCount. - List a record's attachments (
list). Reads the record and returns the attachments of the chosen field with their download link. Publishesfound,recordId,attachments,count,urlExpiresInMinutes. - Fetch a record's attachments into the run (
download). Reads the record, then downloads each attachment from Airtable's signed link and adds it to the run's attachments, so that a Compose step can attach it by its position. At most 10 files per step and Maximum size per file (MB) (maxDownloadMb) each. Publishesfound,recordId,attachments,positions,count,skipped.
Base (resource: base, operation in baseOperation)
- Get a base schema (
schema, the default). Reads the tables of Base, with their fields and views and their ids (GET /v0/meta/bases/{base}/tables). PublishesbaseId,tables,count. - List the bases (
list). Lists the bases the token can reach, with their permission level (GET /v0/meta/bases). Publishesbases,count,truncated.
Every operation also publishes simulated and summary.
Example
Each new client request must create one row in a "Cases" table, and only one, even if the email is processed twice. An Extract step named "Extract" has already found the case reference in case_ref. The Airtable node is named "Case record", so its data lives under case_record:
text
resource record
recordOperation upsert
base (list mode) Firm cases
table (list mode) Cases
mergeField (list mode) Case_ref
values Case_ref → {{ data.extract.case_ref }}
Client_email → {{ email.from.email }}
Last_subject → {{ email.subject }}
typecast offFor a reference seen for the first time, the step data reads:
json
{
"records": [
{ "id": "recA1b2C3d4E5f6G7", "createdTime": "2026-10-04T08:12:40.000Z",
"fields": { "Case_ref": "2026-0412", "Client_email": "marie@example.com", "Last_subject": "New request" } }
],
"recordId": "recA1b2C3d4E5f6G7",
"fields": { "Case_ref": "2026-0412", "Client_email": "marie@example.com", "Last_subject": "New request" },
"createdIds": ["recA1b2C3d4E5f6G7"],
"updatedIds": [],
"createdCount": 1,
"updatedCount": 0,
"created": true,
"mergeFields": ["fld…"],
"warnings": [],
"simulated": false
}When the next email about case 2026-0412 arrives, the same row is updated: created is false and updatedIds holds its id. A Condition (If) on {{ data.case_record.created }} sends the acknowledgement of receipt only for new cases.
Tips
- Prefer Create or update to Create. Create is not idempotent: if the engine replays the step after an incident, a second row is added. Create or update on a business key (case reference, email address,
Message-ID) gives the same row back on every replay. The key must be unique in the table: if several rows share the value, Airtable rejects the request. Delete and Comment are not protected either: a replayed Comment posts the comment twice. - Field names in expressions. Expressions only reach keys made of letters, digits,
_and$. A column namedClient emailcannot be read with{{ data.<step>.fields.Client email }}: name the columns you read in workflows without spaces or accents, or read the wholefieldsobject. - Empty values. Airtable leaves empty fields out of its answers (
"",[],false), so a field can be missing fromfieldsrather than empty. - Conditions are escaped. Values typed in Conditions are escaped before being put into the formula, so an email subject cannot change the filter. With Airtable formula, escaping values that come from an email is up to you. Dates are written
2026-09-22; is older than (days) and is newer than (days) compare with today. - Unordered results. Without Sort by and without a view, the order of the rows returned by Airtable is arbitrary.
- Automatic conversion. With Automatically convert values, Airtable creates missing select options and missing linked records: useful to map an AI output onto a select, risky on a well-kept base, where a typo becomes a new option.
- Uploads. Airtable accepts at most 5 MB per uploaded file: larger files are skipped and listed in
skipped, the others are still sent. Replaying an upload appends the files again. - Links that expire. The
urlreturned by List a record's attachments expires after two hours: use it right away, never store it. To keep or forward a file, use Fetch a record's attachments into the run instead. - Download size. Maximum size per file (MB) goes from 1 to 10, the server's real limit per downloaded file. A file over the limit fails the step.
- Get a record. Only an unknown record gives
found: false. Any other error answer fails the step with its own code: a refused token withintegration.unauthorized(reconnect), a quota or an outage with an automatic retry. - Test runs. In test runs, Search, Get a record, the schema and the attachment list and fetch run for real. Create, Create or update, Update, Delete, Comment and Upload are only described: nothing is written,
simulatedis true, no record id is returned, and Create or update reportscreated: false. - Rate limit. Requests are paced to 4 per second per connection, under Airtable's limit of 5 per second per base. After a 429, Airtable makes you wait 30 seconds: the node honours its
Retry-Afterwithin four attempts, then the step fails withintegration.rate_limitedand the engine retries it later. - Errors.
integration.unauthorized: the token was refused, lacks a scope, or no longer reaches the base; check its scopes and bases at airtable.com/create/tokens.integration.not_found: the base, table or record does not exist or is outside the token's access.integration.rejected: Airtable refused the request (unknown field, value of the wrong type, several rows matching the merge key).node_invalid_param: a required choice is empty, or Fields as JSON is not valid JSON. See Error handling.