Airtable to Postizdraft-only social posts for a seasonal pop-up bar
Copies ready posts from a content calendar into a social scheduler as drafts, so a human is the only one who can publish.
- State
- Script built. The daily routine has not run yet.
- Written
- Time to build
- Not recorded
- For
- A seasonal pop-up bar
Important: no daily routine has run yet. The script exists, but the drafts so far were built in batches by hand-directed sessions. Anything below about a scheduled routine is the plan, not a run history. Numbers come from the project lead’s session notes and a snapshot of the content calendar, checked 2026-10-28.
Problem
The pop-up’s brand partner publishes a social content calendar in Airtable: one row per post, with a date, platform, caption, status, and notes that point to an image file. Someone has to take each ready row, find the right image, write it into the social scheduler, and avoid posting the same thing twice. Over a roughly three-month season, that is dozens of small copy-and-paste jobs, and a missed or doubled post is public.
The goal: let the computer do the copying, and keep the human as the only one who can publish.
Stack
- Airtable: the content calendar (read only).
- Postiz: the social scheduler, driven through its connector and command line tool. The pop-up’s Instagram account is its own channel, separate from other projects’ channels.
- A Python script (about 280 lines) that does the filtering, image lookup, upload and draft creation.
- A planned scheduled routine to pull the Airtable rows and hand them to the script as JSON. It has not run.
- A local JSON file used as a ledger by the script.
- Postiz’s web UI, used by hand to attach images.
Steps
How the script is designed to work, read from the code:
- Input. Airtable rows come in as JSON. The script reads six fields: Post (the title), Platform, Date, Status, Caption, and Notes.
- Window. By default, today through the next 7 days, or a start and end date you pass in.
- Filters. A row becomes a candidate only if all of these are true. Each failure goes in a “skipped” list with the reason:
- Status is exactly “Ready”.
- Notes do not start with “FLAG” (a human has a question about the row).
- The default draft time on the post date has not already passed.
- Exactly one platform is selected.
- Feed and Reel rows have a caption. Story rows may have an empty caption.
- The row is not already in the ledger.
- Notes contain a line starting “Asset file:” naming the image, and the script can find that file.
- Notes parsing. “Asset file:” names the image. “Hashtags:” is appended to the caption as its own paragraph.
- Finding the image. Local assets folder first, then the unpacked partner files, then inside the partner’s zip downloads.
- Duplicate check against Postiz. Before creating anything, it lists existing posts for that channel and date range. A feed post is a duplicate if it is on the same channel, within one day, and starts with the same first 60 characters of caption. A story is a duplicate if there is already a story that date.
- Create the draft. Upload the image, then create the post with type “draft”. The type is a constant and the script asserts it equals “draft” right before sending. “Schedule” and “now” never appear as a post type.
- Ledger. After each draft, the script records the Airtable row ID and the Postiz post ID, written to a temp file and swapped in so a crash can’t half-write it.
- Safety valves. A cap of 20 drafts per run, a dry-run flag, and an exit without creating anything if the existing-post list can’t be read.
- Report. A JSON report with three lists: created, skipped (with reasons), and errors.
- Human step. The owner opens Postiz, reviews each draft, and schedules or publishes it. Nothing is published or scheduled without an explicit yes.
What actually happened
Drafts were created in a few hand-directed sessions that read the Airtable rows read-only plus the partner’s graphics. The slow part was image attachment, not text. Real brand graphics only (no AI images). Photos with unconfirmed usage rights were left out.
Real numbers
Source: the project lead’s session notes, checked 2026-10-28.
- 29 drafts now in Postiz: 26 Instagram and 3 Google Business. All are drafts; none are published or scheduled live. Counted from the project lead’s session notes, not re-pulled from the live list.
- 0 daily routine runs so far.
- Time saved: not measured. No minutes-saved figure exists, so none is claimed.
Source: the content calendar snapshot (2026-10-28), structure and counts only.
- 80 rows over roughly three months.
- By status: 48 Ready, 29 In Progress, 3 Idea.
- By platform: 48 Instagram Feed, 29 Instagram Story, 3 Instagram Reel. All 29 Stories are In Progress, so the “Ready only” rule keeps them out.
- 3 rows carry a FLAG in the notes, all 3 marked Ready, which is why the FLAG check exists.
- Image check by filename against the partner’s zip downloads: of 48 Ready rows, 22 match and 26 don’t. A rough check; the script’s own lookup is what counts.
- The script’s own ledger holds 1 draft. The other drafts came from the batch sessions, so the ledger is not the count of drafts.
The 29 drafts include 3 Google Business posts. The script only handles Instagram, so it did not make those.
Misfires
- No local image upload in the connector. Postiz’s connector creates text-only drafts. Images were attached afterwards by hand in the Postiz web UI, one post at a time.
- No public file host route. The connector’s URL upload accepts only public URLs. Uploading to anonymous public file hosts was blocked by the safety review, correctly, so that route was dropped.
- UTC and daylight-saving error. Post times are stored in UTC. One draft was created at 22:00 UTC instead of 17:00 UTC (noon local) after the clocks changed. It was deleted and recreated. Rule: always compute UTC from local time with the DST date in mind.
- Text-only list previews. The list view shows only text, so attached images could not be confirmed through the connector. The report from whoever attached them was relied on.
- One caption typo still open. A hashtag typo is in one draft. Fixing it means recreating the draft.
- Missing assets. Many rows name an image the script can’t find (26 of 48 Ready rows by a filename check). The script skips these, which is safe, but those posts wait until someone supplies the file.
- Story captions. Story rows have no caption and are all In Progress. The story path is untested end to end, and the story duplicate check looks only at the date, so two different stories on one day would count as one.
- Flagged rows marked Ready. Three rows are Ready and flagged. The script skips them correctly.
- Loose filename matching. Images are found by file name alone, ignoring folders and case.
- Same-day cutoff. A row for today is skipped if its draft time has passed.
- Channel reconnect: unconfirmed. The project’s Instagram account was added to Postiz as its own channel by the owner, separate from other projects’ channels. Whether a social account split needed a reconnect is not known, so nothing is claimed either way.
Copy-paste template
The Airtable fields to set up, so a script like this can read your calendar:
| Field | Type | Rule |
|---|---|---|
| Post | Text | Short title |
| Platform | Single select | Exactly one value per row |
| Date | Date | The day the post goes live |
| Status | Single select | Only “Ready” is picked up |
| Caption | Long text | Required for feed posts |
| Notes | Long text | Line 1: “Asset file: [exact file name]”. Line 2: “Hashtags: [your tags]”. Start with “FLAG” to hold a row |
A starting-point routine prompt. This is a reconstruction from how the script is built, not a prompt that has run:
Every day at [TIME], read the [CONTENT CALENDAR NAME] table from Airtable. Pass every row, with its fields, to [SCRIPT PATH] as JSON.
Rules you never break:
- Drafts only. Never schedule, never publish.
- Skip any row whose status is not Ready, and any row whose notes start with FLAG.
- Never create a post that already exists. The script checks the ledger and the scheduler; do not work around it.
- Compute the UTC time from the local time, with the daylight-saving date in mind.
Then report: how many drafts were created, which rows were skipped and why, and any errors. If nothing was created and nothing is wrong, say so in one line.
The rules to give your own agent: drafts only, ready rows only, hold anything flagged, check both a ledger and the scheduler before creating, cap the batch, compute UTC from local time with DST in mind, and report every skip with a reason.
What’s next
- Start the daily routine and capture its first real runs. Until then, no routine claims.
- Fix the open hashtag typo (recreate that draft).
- Confirm whether the social account split needed a Postiz reconnect.
- Find a way to attach images without the web UI, or accept it as a manual step.
- Finish the Story path.
- After a week of runs, add real numbers: drafts created, drafts scheduled unchanged, drafts edited, rows skipped and why.
- Settle one default draft time for the routine.
Live link or demo
No public live link. The recorded demo at the top of this page shows it end to end. A simulated run on made-up calendar rows for a sample pop-up bar, built to show how the script is designed to work. No daily routine has run yet. 38 seconds, no sound.