Python + GitHub Actions delivery path for an n8n content engine (WordPress/FluentCRM, 2 instances)
What we need
One of two parallel tracks on a documented content-automation project. A formal requirements document with numbered requirements and an acceptance matrix is shared with shortlisted experts. This is the Python / GitHub Actions delivery track; the other covers the n8n engine. Bid on one or both. CONTEXT A client runs two small businesses (one regulated). Each gets its own instance: its own GitHub organisation and private repository, environments and secrets, WordPress site and FluentCRM. Shared code, per-instance configuration. Everything runs on accounts the client owns, and no production credential stays with the vendor after handover. The system creates drafts only, with one exception: the web version of a campaign a human has already sent. WHAT YOU OWN The other track generates content in n8n and opens a PR adding one Markdown file with YAML front matter to staged-content/. From there it is yours: validate the PR, ingest the merged file into WordPress or FluentCRM as a draft, archive it, publish web versions of sent campaigns, call back to n8n over a signed webhook, and run GitOps for the n8n workflows. SCOPE 1) Five Python CLI scripts, each with a --dry-run flag doing every read and lookup but no write, JSON on stdout, non-zero exit on failure, config and secrets from environment variables only: - validate: front matter against a JSON schema, changed-path check, read-only slug and send-date lookup in the CMS, one issue per send date. - ingest: list files the push added or modified, convert Markdown to WordPress HTML (classic or block markup, your call to justify), render newsletters into the FluentCRM template as email-safe inline-styled HTML, check for an existing item, create the draft, assign the next issue number inside the concurrency group. - archive: move the file to archived-content/<year>/<month>/, write CMS id, URL, issue number, ingestion time and run URL into front matter, commit as a GitHub App without restarting ingestion. - webversion: detect sent campaigns, fetch the body as sent, strip the template wrapper, unsubscribe and preference links, tracking pixels and per-subscriber content, resolve or fail on merge tags, commit the cleaned body into the archive, publish the post idempotently. - callback: HMAC-sign the result and send it to n8n, including from a failed job. The n8n deploy and drift-check scripts follow the same rules. 2) GitHub Actions workflows Validation on every content PR; ingestion on pushes to main touching staged-content/, processing only the files that push changed; a concurrency group so two runs never touch the same file; a scheduled sweep retrying stuck files to an attempt limit then reporting; CI running pytest and linting on every code PR. Pinned runner images and Python, actions pinned to commit SHAs, dependencies pinned with hashes in a lockfile. Workflow files hold no business logic: checkout, setup, install, call a script. The scripts must also run from a plain shell with the same environment variables, so the client can move ingestion off Actions later without a rewrite. 3) Idempotency and collisions Before creating anything, look up the slug in any status and the run identifier (posts) or the send date and issue number (campaigns). An item this engine created for the same run identifier means record and archive, create nothing. Any other item is a collision: leave the file staged and report it. Slug suffixing is not a collision detector. 4) n8n GitOps The repository is the source of truth for every n8n workflow. A deploy workflow calls the n8n public API on merge, matching workflows through a per-instance mapping file and never by name, deploying in dependency order, writing new ids back by a GitHub App commit, recording release version and commit SHA, rolling back by redeploying a previous tag. A scheduled drift check exports the live workflows, normalises them and opens a PR for any difference. 5) Integration surface WordPress and FluentCRM REST plus a small PHP connector plugin a separate supplier builds. You write its interface specification (routes, parameters, response shapes, error codes, required capability, a fixture per route) and a conformance test either party can run against a site; you do not build the plugin. A phase-1 spike per site establishes FluentCRM version and status values, which fields REST can actually set, and whether Cloudflare, Cloudways bot protection and Wordfence admit REST calls from GitHub-hosted runners. HARD CONSTRAINTS No code path may publish a post, send, schedule, resume or duplicate a campaign, or change the status of an item it did not create; CI fails any PR that introduces one. Web versions are the only publish. No secret in a workflow parameter, file, log, row or email. ACCEPTANCE PER INSTANCE Every merged file ingested and archived within 30 minutes of merge or reported; nothing left in staged-content/ past the sweep limit without a failure report; re-runs and concurrent runs create no duplicate; across four sent issues each web version is published within an hour, matches the sent body, carries no subscriber data or unresolved merge tags and exists exactly once; the conformance test passes on each site; a workflow change goes PR to merge to live n8n with no manual import, and the drift check catches a direct edit. DELIVERABLES Scripts with unit tests and CI, Actions workflow files, front-matter schema, connector specification and conformance test, per-instance configuration, a runbook (stuck file, failed web version, credential rotation, deploy and rollback, drift report, collision), recorded handover plus a live session per operator. IP assigns on final payment. PROPOSAL Phases: discovery and site spike, build plus first instance, second instance, stabilisation (budgeted at no less than the build phase), handover. Give a duration and fixed price per phase, a support retainer option, and two comparable deliveries including what broke during stabilisation.