@excelium-tech/granola
v0.3.4
Published
Granola meeting-notes connector for Twenty
Readme
Granola for Twenty
Sync Granola meeting notes into your Twenty CRM. For every
meeting, this app creates a Note on each participant Person (so the meeting
is visible on Person, Company, and Opportunity pages) and a CallRecording
carrying Granola's AI summary and transcript, linked to the matching
CalendarEvent — searchable, in context, and ready for AI agents and workflows
to act on.
What this app does
- Granola records and summarizes your meeting.
- On a schedule (every 15 minutes) this app polls the Granola REST API for notes updated since the last window, and on install it backfills the last 30 days.
- For each note it fetches the full detail (summary, transcript, attendees,
calendar event) from
GET /v1/notes/{id}?include=transcript. - It resolves attendee emails to existing
Personrecords and upserts:- a
Note(title + a link back to Granola + the AI summary markdown) with oneNoteTargetper resolved participant, and - a
CallRecording(one per Granola meeting) with the AI summary and the transcript, linked to the matchingCalendarEventwhen one is found.
- a
All writes are idempotent: deterministic ids mean re-syncing a meeting updates the same records instead of duplicating them.
Calendar event matching
Granola notes carry a calendar_event object. The app matches it to a Twenty
CalendarEvent with two strategies (mirroring the platform's calendar sync):
- External event id — the note's provider event id
(
external_id/google_calendar_event_id) is matched againstCalendarChannelEventAssociation.eventExternalId. - iCalUID — the note's
ical_uidis matched againstCalendarEvent.iCalUid.
If no calendar event matches (the meeting was never on a synced calendar, or
calendar sync is not configured in Twenty), the CallRecording is still created,
just without a linked calendar event, and the Note still delivers Person-page
visibility.
Transcript
Granola's transcript is a flat string segmented only by coarse audio-source
markers (Microphone: for the local mic, Speaker: for remote audio) — there
are no timestamps and no real attendee names. The app parses it into ordered
speaker turns at write time and renders a simple scrollable list on the calendar
event's Transcript tab. The AI Summary tab is the primary surface.
Setup
| Step | Where |
| ---- | ----- |
| 1. Get a Granola Business-plan API key | https://granola.ai → API settings |
| 2. Install the Granola app in Twenty | Settings → Apps → Granola → Install |
| 3. Paste the key into the GRANOLA_API_KEY application variable | Twenty app settings |
On install the app backfills the last 30 days; after that the cron keeps it up to date.
Configuration
| Application variable | Purpose |
| -------------------- | ------- |
| GRANOLA_API_KEY (secret) | Bearer token for https://public-api.granola.ai/v1. Used to list notes and fetch each note's transcript, summary, attendees, and calendar event. |
Notes and limitations
- Polling, not webhooks. Granola does not expose webhooks, so completeness relies on an incremental overlap window plus a once-daily 24h re-sweep. A note updated only during a longer outage is picked up by the next daily sweep or a manual re-install/backfill.
- Plain transcript. No diarization or timestamps (Granola limitation).
- Person matching by email. A participant only gets a
NoteTargetonce a matchingPersonexists in Twenty (by primary email). Unmatched participants are linked on a later run after Twenty's own contact sync creates the Person. - No custom fields in v1. The Granola meeting id is stored in the standard
CallRecording.externalRecordingId; the Granola web URL is carried in the Note body.
