• Townie
    AI
  • Blog
  • Docs
  • Pricing
Log inSign up
nbbaier

nbbaier

attio-slack-summaries

Remix of stevekrouse/attio-slack-summaries
Public
Like
attio-slack-summaries
Home
Code
9
core
5
scripts
2
.vtignore
README.md
C
alert.ts
formatters.ts
slack.ts
types.ts
H
webhook.ts
Branches
4
Pull requests
Remixes
1
History
Environment variables
2
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.
Sign up now
Code
/
README.md
Code
/
README.md
Search
9/9/2025
Viewing readonly version of simplification branch: v530
View latest version
README.md

Attio List Slack Notifications

This val sends you customizable Slack notifications about your Attio lists.

It receives Attio webhooks, enrichs the data via the Attio API, stores them in a SQLite database, and periodically sends notifications about recent activity to Slack.

Sample of Slack notifications

This val is a remix of @stevekrouse's attio-slack-summaries val. That val sent many more events through and did not handle comments, which this val now does. In addition, the scope of notifications that you'll receive is much more limited. See the final section ("What will you get notifications about?") for more information.

Set up

  1. Remix this val
  2. Get a Slack webhook & set it as SLACK_WEBHOOK_URL in this val's Environment variables in the left sidebar
  3. Get an Attio Access Token (with all read & write permissions) & set it as ATTIO_API_KEY in this val's Environment variables in the left sidebar
  4. Configure your lists in webhook.ts:
    • Update the listIds array with your Attio list IDs
    • To find your list's id, navigate to the list and copy the path segment after collection: https://app.attio.com/<workspaceName>/collection/<listId>
  5. Run setup.ts to set up the database and Attio webhook
  6. Run bootstrap.ts to initialize the database with the current state of your configured lists.
  7. Go trigger some Attio events and see the message in Slack! (If you want them faster, you can run alert.ts manually.)

Customization & Configuration

The system is designed to require as little configuration as possible: all you need to do is add your list ids to the listIds array in webhook.ts, run the bootstrap script and you're good to go. However, I have tried to organize the codebase for easy customization:

  • User-facing files (formatters.ts, slack.ts)
  • Internal logic is isolated in core/ directory
  • Type safety ensures custom formatters match Attio attribute types

The sections below cover the customization options.

Adding Lists

To track additional Attio lists, add their IDs to the listIds array in webhook.ts:

export const listIds = [ "first-list-id", "second-list-id", // Add more list IDs here ];

After adding new lists, run bootstrap.ts to initialize them in the database.

Note: removing a list id from listIds will not remove existing data from the database, but it will prevent it from being stored in the future.

Customizing Attribute Formatters

The formatters.ts file controls how Attio attribute values appear in Slack messages. Formatters are functions that take an object of a type in AttioAttributeValue and return a string. Type-safety ensures that custom formatters match Attio attribute types. The formatter file is organized into sections for easy customization:

Text & Selections:

const textFormatters = { text: (av: AttioTextValue) => av.value, status: (av: AttioStatusValue) => av.status.title, select: (av: AttioSelectValue) => av.option.title, checkbox: (av: AttioCheckboxValue) => av.value.toString(), };

Numbers & Currency:

const numberFormatters = { currency: (av: AttioCurrencyValue) => { return new Intl.NumberFormat("en-US", { style: "currency", currency: av.currency_code, }).format(Number(av.currency_value)); }, number: (av: AttioNumberValue) => av.value.toString(), };

Dates, People, Links, etc. - Each category has its own section with examples showing input / output.

Customizing Slack Messages

The slack.ts file controls Slack message appearance. See the file for more examples and details.

Message Settings:

const SLACK_CONFIG = { maxCommentLength: 150, // Comment truncation groupByEntry: true, // Group changes by entry (as in the image above) allowEmojis: true, // Enable emoji in headers };

Message Templates:

const MESSAGE_TEMPLATES = { entryCreated: (actor: string, name: string) => `${actor} added ${name}`, entryDeleted: (actor: string, name: string) => `${actor} removed ${name}`, fieldChanged: (field: string, oldValue: string, newValue: string) => `*${field}*: ${oldValue} → ${newValue}`, // ... more templates };

Link Formats:

const LINK_FORMATS = { entry: ( workspaceSlug: string, objectSlug: string, recordId: string, name: string ) => `<https://app.attio.com/${workspaceSlug}/${objectSlug}/${recordId}/overview|${name}>`, // ... more link formats };

Project Structure

attio-slack-summaries/
├── webhook.ts                   # Main HTTP endpoint - receives Attio webhooks
├── alert.ts                     # Cron job - processes events & sends Slack messages
├── types.ts                     # Core type definitions
├── formatters.ts                # Attribute value formatters (user-customizable)
├── slack.ts                     # Slack integration (user-customizable)
├── core/                        # Internal system logic
│   ├── alert-processor.ts       # Event processing & message creation
│   ├── api-client.ts            # Attio API client
│   ├── auth.ts                  # Webhook authentication & setup
│   ├── database.ts              # SQLite operations
│   └── webhook-handler.ts       # Event routing & state management
└── scripts/                     # Setup & maintenance scripts
    ├── setup.ts                 # Creates database & Attio webhook
    └── bootstrap.ts             # Seeds initial state data

Template Files (designed for user customization):

  • formatters.ts - How attribute values display
  • slack.ts - Message appearance & templates
  • webhook.ts - List configuration

Core Files (internal system logic):

  • core/ directory - Processing, API, database operations
  • types.ts - Type definitions for the entire system

Setup Files:

  • scripts/ directory - One-time setup and maintenance

How it works

The system uses Attio webhooks to receive real-time notifications about changes to your lists. Each webhook event triggers:

  1. Event Reception webhook.ts - Validates and filters incoming events
  2. State Storage webhook-handler.ts - Stores current state in SQLite
  3. Change Detection alert-processor.ts - Compares states to detect changes
  4. Message Generation formatters.ts - Formats changes into human readable strings
  5. Slack Delivery slack.ts - Sends formatted messages to Slack

Attio webhooks return very thin events. They contain only IDs, not human-readable data or change details. In order to send human readable messages to Slack, we need do a lot of work to enrich the events with data fro the Attio API.

The hardest part of this project was transforming these events into data that could be used to track the state of list entries and comments. I've accomplished this here by getting the state of the entry/comment when a webhook event comes in, storing that state in the database. We go from a webhook event that looks like this:

{ "event_type": "list-entry.updated", "id": { "workspace_id": "d1cf50ae-67ee-4109-b25a-d6223d6a780f", "list_id": "ea3b198f-859a-41fa-bb39-e0972cb71051", "entry_id": "255c52fc-85e8-4d90-8127-c6b139e3ca37", "attribute_id": "a6336f45-006c-4f08-b371-14ee9546ebee" }, "parent_object_id": "54439a10-8518-49e7-b268-41663063cdab", "parent_record_id": "6bc23aa2-5c5c-4341-949f-9177fd0e6bd5", "actor": { "type": "workspace-member", "id": "f2bbd1ae-29b5-4020-9590-f9afe2b4a276" } }

To a typescript state object that looks like this:

Create val
// Stored state includes complete API response const stateData = { id: "255c52fc-85e8-4d90-8127-c6b139e3ca37", type: "entry", action: "updated", timestamp: 1757439109123, listId: "ea3b198f-859a-41fa-bb39-e0972cb71051", entryId: "255c52fc-85e8-4d90-8127-c6b139e3ca37", rawData: { // Stored state includes complete API response id: { ... }, parent_record_id: "6bc23aa2-5c5c-4341-949f-9177fd0e6bd5", parent_object: "companies", created_at: "2025-09-09T16:55:11.986000000Z", entry_values: { // Crucially, includes data about the entry's attributes stage: [ { active_from: "2025-09-09T16:55:11.986000000Z", active_until: null, created_by_actor: { ... }, status: { id: {... }, title: "Prospecting", ... }, attribute_type: "status", }, ], // more attributes... }, metadata: {...}, };

Messages are generated by comparing consecutive states to detect changes. Diffing and message generation is done at run-time when the cron job triggers. This cuts down on the complexity of the database schema (differences and messages are not stored).

What will you get notifications about?

Currently, the system only tracks the state of two things: list entries and comments on those list entries. The Attio webhook is set up to accept the following events:

  • list-entry.created
  • list-entry.deleted
  • list-entry.updated
  • comment.created
  • comment.resolved
  • comment.unresolved
  • comment.deleted

The logic of generating messages is not set up to handle states outside these two categories. The database is not set up to store data related to things other without both a list id and an entry id (see here).

This is especially relevant for comments. Attio comments can be associated with a record or a list entry (among other things). When a comment is added to a record, it is associated with the record, not the list entry. When a comment is added to a list entry, it is associated with the list entry, not the record. Because of this, you will not see notifications for comments on records, even if those records are in a list you track.

See this Attio help center article for more on Attio's data model. See this Attio API documentation for more information on the types of events that Attio can send.

FeaturesVersion controlCode intelligenceCLI
Use cases
TeamsAI agentsSlackGTM
ExploreDocsShowcaseTemplatesNewestTrendingAPI examplesNPM packages
PricingNewsletterBlogAboutCareersBrandhi@val.townStatus
X (Twitter)
Discord community
GitHub discussions
YouTube channel
Bluesky
Terms of usePrivacy policyAbuse contact
© 2025 Val Town, Inc.