PR CRM — Attio Workflows
PR CRM — Attio Workflow Guide
Section titled “PR CRM — Attio Workflow Guide”Attio is the team’s day-to-day surface for contact + campaign tracking. Supabase is the master database underneath; Attio is a polished mirror you actually click around in. Every change you make in Attio syncs back to Supabase via webhook, and every send/open/reply/bounce reported by Smartlead flows into Attio in real time.
This guide covers how to use Attio to its full potential for per-campaign tracking, daily triage, and relationship management.
The data model in plain English
Section titled “The data model in plain English”Three objects in Attio matter for PR work:
| Object | What it is | What lives there |
|---|---|---|
| People | Every contact in the database (~13K). Mix of journalists, producers, influencers, editors. | Name, email(s), outlet, role, segment, beat, market(s), pitch_status, snoozed_until, last_contacted_at, last_replied_at, bounce_count, verified, social handles, notes |
| Campaigns | One record per pitch you’ve sent (or are sending). | Name, status, start_date, segment_filter_summary, smartlead_campaign_id, sent_count, open_count, reply_count, bounce_count, members |
| Markets | Hierarchical geography (countries → states → cities). | Name, type, parent market |
The two relationships you care about:
- People ↔ Campaigns (bidirectional): every campaign has a
memberslist of people; every person has anAssociated campaignslist. Adding someone to one side automatically reflects on the other. - People ↔ Markets (multi-reference): a person can be tagged with multiple markets (Miami, Florida, USA national).
Five core workflows
Section titled “Five core workflows”1. Browse a campaign’s roster
Section titled “1. Browse a campaign’s roster”Open Campaigns → click into any campaign (e.g. “Palace: May Editorial Pitch”). Scroll to the Members section. Every person we pitched in this campaign is there. Click any person to drill into their profile.
2. Filter People by their pitch state
Section titled “2. Filter People by their pitch state”Attio’s filter UI on the People object lets you compose conditions across any attribute. Useful patterns:
- By campaign membership:
Associated campaignscontains “Palace: May Editorial Pitch” - By state:
pitch_status=bounced(orreplied,snoozed,unsubscribed,do_not_contact) - By recency:
last_replied_atis greater than “14 days ago” - By market:
marketscontainsFlorida - By segment/beat:
segment=Editorial,beat=Food
Combine conditions with AND/OR. Sort by any attribute. Then save the result as a View so you can revisit it without rebuilding.
3. Per-person campaign history
Section titled “3. Per-person campaign history”Click any person in Attio → scroll to the Associated campaigns section. You see every campaign they’ve been in, ordered by date. Click any campaign to drill back into its details. This is your “have we pitched this person recently?” view.
Use the Notes section on the person record to log relationship context (preferences, past wins, contact preferences, do-not-pitch reasons).
4. Snooze a person
Section titled “4. Snooze a person”In a person’s record, set:
pitch_status=snoozedsnoozed_until= the date they should be eligible again
The webhook syncs to Supabase. Future campaign filters automatically exclude them. The daily 7am UTC unsnooze cron flips them back to active once the date passes.
This is a global snooze — applies across all future campaigns, not just one. Per-campaign snooze isn’t supported yet (see Where the model is thin).
5. Mark someone as do-not-contact / unsubscribed
Section titled “5. Mark someone as do-not-contact / unsubscribed”For people who explicitly said “stop emailing me” or who unsubscribed via Smartlead’s footer link:
pitch_status=unsubscribed(Smartlead unsubscribes flow here automatically via the webhook)pitch_status=do_not_contact(manually, for explicit opt-outs)
Both states permanently exclude the person from all campaign filters.
Saved Views cheatsheet
Section titled “Saved Views cheatsheet”Set these up once and pin them. They become your daily triage surface.
On the People object
Section titled “On the People object”| View name | Filter | Sort | Use |
|---|---|---|---|
| Campaign: [Pitch Name] | Associated campaigns contains " | last_replied_at desc | One per active campaign. Shows the roster + replies floating to top |
| Replies — last 14 days | last_replied_at > “14 days ago” | last_replied_at desc | Morning triage. Reply to people while it’s still fresh |
| Replies to [Pitch Name] | Associated campaigns contains "last_replied_at > campaign start date | last_replied_at desc | Closest proxy to “replied to THIS pitch” |
| Bounces in [Pitch Name] | Associated campaigns contains "pitch_status = bounced | — | Per-campaign deliverability check |
| Snoozed — coming back soon | pitch_status = snoozed AND snoozed_until < “30 days from now” | snoozed_until asc | Anticipate who’s about to become eligible |
| Florida — never pitched | markets contains Florida AND pitch_status = active AND last_contacted_at is empty | — | Find fresh prospects when designing a new campaign. Replace “Florida” with any market |
| VIPs | beat = VIP | last_replied_at desc | Concierge view of priority contacts |
| Pipeline (kanban) | (no filter) | Group by pitch_status | Visualize 13K contacts as a kanban. Drag-and-drop to update status |
| Editorial Food — Florida | markets contains Florida AND segment = Editorial AND beat = Food | — | Pre-saved campaign segments. Replace with any market/segment/beat combo you pitch often |
On the Campaigns object
Section titled “On the Campaigns object”| View name | Filter | Sort | Use |
|---|---|---|---|
| Active campaigns | status = sending | start_date desc | What’s actively going out right now |
| Performance leaderboard | (none) | reply_count desc | Quick scan of which historical campaigns landed |
| Palace campaigns | name contains “Palace:“ | start_date desc | Per-client roll-up. Make a parallel one for each client |
| Drafts to launch | status = draft | created_at desc | Pre-launch staging area |
Customizing Views
Section titled “Customizing Views”Once you have a saved View, use Attio’s column controls to:
- Add columns for the attributes you care about most (sent count, last reply date, beat, outlet)
- Hide columns that aren’t relevant for that view
- Group records by any attribute (status, segment, market) for kanban-style layouts
- Pin Views to the sidebar so they’re one click away
Views can be private (just for you) or shared (whole team sees them). Set this when saving — useful for team-wide views like “Replies — last 14 days”.
Daily routine
Section titled “Daily routine”A reasonable morning rhythm:
- Check “Replies — last 14 days” → respond to anyone you haven’t yet
- Check “Snoozed — coming back soon” → plan upcoming pitches around who’s about to be eligible
- Check active campaigns → eyeball reply/bounce counts on each (run
make sync-campaignsfirst to refresh from Smartlead) - Triage bounces → for any contact with
pitch_status=bounced, decide whether to keep them as-is or correct the email and re-tag
Then for whatever pitch you’re working on:
- Open “Campaign: [name]” → who’s in it, who replied, who needs a follow-up
- Add notes on individual people as you correspond — context lives on the relationship, not in your inbox
Where the model is thin
Section titled “Where the model is thin”last_contacted_at and last_replied_at are global to a person, not per-campaign. So if Sarah was in your March pitch + your May pitch and replied to March, you can’t tell from Attio alone whether she replied to March or May — only that she replied at some point.
For most PR workflows that’s fine — once someone replies, you stop pitching them anyway. But if you want reports like “reply rate of the May Editorial pitch” or “who from the Cherry Blossom DC pitch never opened the email”, you need per-campaign-per-person granularity.
The data exists on the Supabase side already (campaign_contacts join table tracks per-campaign timestamps). Surfacing it in Attio would require a Pitches object (one record per campaign-person pair) — a larger Phase 6 enhancement that’s not built yet. Worth investing in once your team’s reporting needs cross that threshold.
Quick reference: the key attributes
Section titled “Quick reference: the key attributes”People
Section titled “People”| Attribute | Type | Purpose |
|---|---|---|
name (built-in) | personal-name | First + last |
email_addresses (built-in) | email (multi) | Primary + alternates. Smartlead matches on this |
job_title | text | Editor / Producer / etc. |
outlet | text | Publication / network / brand |
segment | select | Editorial / TV / Radio / Online / Podcast / Influencer / Photography / Newsletter / Other |
beat | select | Food / Wine / Travel / Entertainment / Lifestyle / Health / Real Estate / etc. (full list: run make options) |
markets | record-reference (multi) | Geographic tags |
pitch_status | select | active / snoozed / bounced / unsubscribed / do_not_contact |
snoozed_until | date | When a snoozed contact becomes eligible again |
last_contacted_at | timestamp | Most recent send (any campaign) |
last_replied_at | timestamp | Most recent reply (any campaign) |
bounce_count | number | Lifetime bounces. Auto-flips status to bounced at 2+ |
verified | checkbox | Email-verification result |
verify_status | text | Verification message (“ok”, “no MX record”, etc.) |
instagram / twitter / linkedin (built-in) | text | Social handles |
phone_numbers (built-in) | phone (multi) | Phone numbers |
Associated campaigns | record-reference (multi) | Every campaign this person is in |
Campaigns
Section titled “Campaigns”| Attribute | Type | Purpose |
|---|---|---|
name | text | Display name. Convention: Palace: / Planta: prefix |
status | select | draft / sending / paused / completed |
start_date | date | When the campaign first sent |
smartlead_campaign_id | text (unique) | The Smartlead-side ID |
supabase_id | text (unique) | The Supabase-side ID |
segment_filter_summary | text | Human-readable: “Florida · Editorial · Food” |
sent_count / open_count / reply_count / bounce_count | number | Aggregate stats. Refreshed by make sync-campaigns |
members | record-reference (multi) | Every person in this campaign |
Tips and gotchas
Section titled “Tips and gotchas”-
The webhook updates contact-level fields, not campaign-level state. When you click Start on a campaign in Smartlead, the campaign’s status doesn’t auto-update in Attio. Run
make sync-campaignsto pull fresh status + stats from Smartlead. Worth running daily or after any UI action. -
Edits in Attio sync to Supabase within seconds. Changing
pitch_statuson a person fires the webhook → Supabase row updates → next campaign filter automatically includes/excludes accordingly. -
Bulk edits via Attio’s UI are fine. Select multiple people in a View → bulk-update an attribute → all changes flow through the webhook.
-
Dedupe before launching big campaigns. If you suspect duplicate Person records (same human under two emails), run
make dedupe DRY=1from the repo to surface them. The dedupe pass moves notes + market links to the surviving record before deleting the duplicate. -
Snooze ≠ unsubscribe. Snooze is a temporary pause; unsubscribe is permanent. Smartlead’s automated unsubscribe link triggers the latter via webhook.
-
Notes belong on the Person, not the Campaign. Per-campaign annotations would require the Pitches object (not built yet). For now, write campaign-specific context as a note on the relevant Person, mentioning the campaign by name.
-
Don’t manually edit
membersin Attio for active campaigns. The campaign roster reflects what was sent through Smartlead. Adding/removing someone in Attio doesn’t change the email send queue; it just makes the data inconsistent. Use Smartlead’s UI to pause/edit live campaigns.
When to come back to this guide
Section titled “When to come back to this guide”- You’re onboarding someone new to Attio → walk them through “Five core workflows” + “Daily routine”
- You want to find unpitched contacts in a market → “Saved Views cheatsheet” → “Florida — never pitched” pattern
- You wonder why a campaign’s status is wrong → “Tips and gotchas” → run
make sync-campaigns - You hit a wall on per-campaign tracking → “Where the model is thin”
For everything below the Attio surface (creating campaigns, importing CSVs, troubleshooting webhooks, deploying), see RUNBOOK.md.