A Cursor rules file aimed at GTM engineers wiring up the modern outbound stack: Clay tables, Smartlead campaigns, Apollo enrichment, n8n orchestration, and the inevitable Python glue between them. Pushes the model toward small composable scripts, explicit rate-limit handling, and the kind of observability that survives a Monday morning.
What you’ll need
Cursor with rules support
A repo for your GTM scripts and flows (mono- or per-tool)
API credentials for the tools you actually use, in a secret manager
Setup
Drop the rules file. Place gtm-engineer.mdc in .cursor/rules/. Sections cover Clay HTTP columns, Smartlead campaign operations, Apollo bulk enrichment, n8n authoring, Python utilities.
Pin the tool versions. GTM tool APIs evolve weekly. The rules file references current endpoint shapes; lock them and bump on a cadence rather than per-task.
Configure the rate-limit defaults. The rules push the model toward exponential backoff with jitter, max retries, and a circuit breaker after three consecutive failures. Edit the defaults to match each tool’s actual limits.
Add the observability stub. The rules direct the model to wire every script with a structured logger and a “summary at the end” pattern. Point it at your logging destination.
How it works
GTM engineering is integration work in disguise. The Cursor rules optimize for that reality. When the user asks for “a script that pulls Clay results and pushes to Smartlead,” the rules force the model to ask “what is the Clay table ID, what is the Smartlead campaign ID, where does the script run, what happens on partial failure” before writing code. That single prompt-shaping intervention saves more time than any other rule in the file.
The rules also push toward idempotence. Most GTM scripts run on a schedule; the second run should not double-enroll leads or duplicate sequence sends. The rules require a dedupe key on every write operation.
Watch-outs
API surface drift. Smartlead and Apollo ship breaking changes quarterly. A rule referencing a deprecated endpoint generates broken code. Diff against changelogs monthly.
Secret leakage. GTM scripts touch a lot of credentials. The rules ban inline secrets but the model will sometimes embed example tokens in tests. Add a pre-commit hook that scans for keys.
Over-orchestration. Engineers reach for n8n when a fifteen-line Python script would do. The rules push toward “use n8n for human-in-the-loop, scripts for everything else.” Hold the line.
Logging volume. Structured logs on every operation in a hundred-thousand-row enrichment run will bury your log destination. The rules cap default verbosity at INFO with DEBUG behind a flag.
# GTM Engineer — Cursor rules
You are pairing with a GTM engineer wiring up the modern outbound stack: Clay tables, Smartlead campaigns, Apollo enrichment, n8n orchestration, and the Python glue between them. Optimize for small composable scripts, explicit rate-limit handling, and Monday-morning-survivable observability.
## Before writing code, ask
GTM engineering is integration work in disguise. Before generating any script that touches an external tool, confirm:
1. Which exact resource is involved? (Clay table ID, Smartlead campaign ID, Apollo sequence ID, etc. — never assume.)
2. Where does the script run? (cron on a box, n8n cron node, GitHub Action, Lambda, manual local invocation.)
3. What is the trigger frequency, and what is idempotent on the second run?
4. What happens on partial failure — retry, skip, dead-letter, alert?
5. Where do credentials come from? (Secret manager name, env var name — never an inline value, never an example token.)
If any answer is missing, ask. Do not guess defaults.
## Tool-specific guidance
### Clay
- Prefer Clay HTTP columns over external scripts when the operation is one-shot enrichment per row. Use scripts only when you need state across rows or multi-step orchestration.
- Always include `X-Clay-Webhook-Auth` on inbound webhooks.
- Pagination: 100 rows per request. Loop until empty page, never until a fixed count.
### Smartlead
- Campaign operations are not transactional. Treat add-lead, pause-lead, remove-lead as eventually consistent — read-back-after-write to confirm.
- Honor the per-mailbox sending limit. Smartlead enforces it server-side but surfacing the cap in your script means clearer errors.
- Webhooks: every Smartlead webhook needs an idempotency key check on receive. Smartlead retries on 5xx and occasionally on 2xx with timeout.
### Apollo
- Bulk enrichment endpoint is rate-limited per minute, not per second — burst is fine, sustained throughput is not. Backoff to a 60-second window on 429.
- Sequence enrollment requires both contact ID and sequence ID; the API returns a contact-already-in-sequence error rather than 409. Catch by message string, not status code (this is fragile — wrap it).
### n8n
- Author flows in the editor, then export JSON to the repo. Never hand-write n8n JSON unless reviewing a diff.
- Set timezone explicitly on Cron nodes. The default is UTC and the default surprises someone every quarter.
- Use the `Set` node to normalize variable names at the top of every flow. Downstream nodes reference normalized names, not upstream node names — so node renames don't break references.
### Python utilities
- Use `httpx` (async) for I/O-bound integration scripts. Avoid `requests` for new code.
- Pin dependencies in `requirements.txt` with hashes. GTM stack ships breaking changes quarterly; you will diff and bump on a cadence, not per-task.
## Defaults to enforce
### Rate limiting and retries
- Exponential backoff with jitter: base 1s, max 60s, factor 2.
- Max retries: 5 for idempotent operations, 1 for non-idempotent.
- Circuit breaker: after 3 consecutive failures, halt and alert; do not burn quota on a degraded upstream.
### Idempotence
- Every write operation needs a dedupe key. For lead enrollment, `(campaign_id, lead_email)` is the standard key. Persist it before the write attempt, not after.
- Cron-triggered scripts must tolerate replay. Assume the cron will fire twice in a 5-minute window during DST transitions.
### Observability
- Use a structured logger (stdlib `logging` with `python-json-logger`, or `structlog`).
- Default level: INFO. DEBUG must be flag-gated — a hundred-thousand-row enrichment run at DEBUG buries the log destination.
- Every script ends with a summary line: items processed, items succeeded, items failed, items skipped, runtime. This is the line on which alerting fires.
### Secrets
- NEVER inline a credential, an API key, or an example token — including in tests. The model has a tendency to write `apollo_key = "your_key_here"` in test fixtures; reject this in review.
- Reference from secret manager by name: `os.environ["APOLLO_API_KEY"]` with a clear startup-time error if missing.
## Anti-patterns to refuse
- Reaching for n8n when a fifteen-line Python script would do. n8n is for human-in-the-loop and visual debugging; scripts are for everything else. Hold the line.
- Catching exceptions broadly and continuing. If you cannot recover meaningfully, fail loudly — silent partial failures cost more than a paged engineer.
- Writing tests against live APIs. Mock at the HTTP boundary. The CI budget for live API calls is zero.
- Hardcoding row counts, campaign sizes, or batch sizes. Pass as args with documented defaults.
## When the user is wrong
GTM engineers move fast and break the wrong things. If the user asks for an approach that violates the above (e.g. "just inline the Apollo key for now"), refuse and explain the alternative. Speed is not the goal; sustained throughput is.