GET CALENDLY'S AI MEETING NOTES INTO KEAP, AUTOMATICALLY
Note Taker joins your call, writes the summary, pulls out the action items, and emails it to you. Then it dies in your inbox. Here is how to get it into the CRM, onto the right contact, where it can actually trigger something.
THE RECAP IS ALREADY WRITTEN. IT IS JUST IN THE WRONG PLACE.
This is the good kind of problem. The hard part is done for you: Note Taker sat on the call, produced a summary, separated the action items by attendee, and sent it over. It is genuinely useful writing that you did not have to do.
And it lands in an inbox, where it is read once and never seen again. The next person to open that contact record in your CRM has no idea the call happened. The onboarding that should have started did not. The notes you would want before the follow up call are twelve emails deep.
The recap is not the deliverable. It is a trigger that nobody wired up.
WHY THIS ONE IS DIFFERENT
If you read the booking guide, this will look like a contradiction. That one says do not match contacts on email, ever, because that is where duplicates come from. This one matches on email.
Both are right, because the situations are not the same. At booking time the person is standing in front of you: they are on your page, in a session, and you can carry a signed token that says exactly who they are. When the recap arrives an hour later there is no browser, no session, and no token. There is an email address in a message.
The rule is not "never match on email." It is: carry a token when you have a session, match on email when you do not, and be honest with yourself about which one you are in. Most duplicate contacts come from matching in the first case, where you did not have to.
THE FLOW
Four parts. Two of them are Microsoft's, one is yours, and one is a decision.
1. Note Taker sends the email
Nothing to build. Turn it on for the event type and it does the work. Worth knowing: there is no API for the recap. Calendly's webhooks fire when a meeting is booked or cancelled, not when a summary is written. The email is not a workaround, it is the only event that exists.
2. Power Automate parses it, with Copilot
Trigger a flow when the recap email arrives, and use Copilot inside Power Automate to pull the pieces out: the invitee's email address, the meeting details, the summary, the action items, the discussion.
This is the part that used to be miserable. Parsing a formatted HTML email meant regular expressions against markup that changes whenever the sender redesigns their template. Copilot reads it the way a person would, which is the right tool for a document written for people.
Keep the flow dumb. It parses and it forwards. It does not talk to your CRM, hold API keys, or make decisions. Every piece of logic you put in a visual flow is logic you cannot read in a diff, test locally, or explain to the next person. Parse, post, stop.
3. Post to an endpoint you control
The flow sends one JSON payload to your own endpoint, with a shared secret in a header. Reject anything without it before doing any work at all.
POST /api/discovery-summary
x-webhook-secret: <shared secret>
{
"customer_email": "jane@acme.com", // required: the only identifier we have
"meeting_details": "...",
"summary": "...",
"action_items": "...",
"discussion_points": "..."
}
A shared secret is enough here, and it is worth being clear why, because the booking guide insists on a signed token instead. There, the caller is a browser: the public, holding your JavaScript, able to change any value in it. Here, both ends are yours. The secret proves the caller is your flow, and your flow is not trying to trick you.
4. Resolve by label, write, tag
Now the endpoint does three things: find the contact by email, write the sections into custom fields, and apply a tag.
The middle step is the one worth stealing. Do not hardcode custom field IDs. They are different in every Keap application, so hardcoded IDs mean your integration only works in the app you wrote it in. Labels are stable. Resolve the IDs by label at runtime from the contact model and the same code works anywhere, and adding a field is a change in Keap rather than a deploy.
// IDs vary per app; labels do not. Resolve at runtime, not at deploy time.
const model = await keap("GET", "/contacts/model");
const idByLabel = {};
for (const f of model.custom_fields) {
if (f.label) idByLabel[f.label.trim().toLowerCase()] = f.id;
}
const fields = [], notFound = [];
for (const m of FIELD_MAP) { // body key -> exact Keap label
const val = body[m.key];
if (!val) continue; // skip empty sections, do not blank them
const id = idByLabel[m.label.toLowerCase()];
if (id == null) { notFound.push(m.label); continue; }
fields.push({ id: Number(id), content: String(val) });
}
// return notFound in the response — a label typo should be visible, not silent
Then the upsert. Match on email with duplicate_option=Email so an existing contact is updated rather than
duplicated, and put opt_in_reason inside the email address object, not next to it.
POST /contacts?duplicate_option=Email
{
"email_addresses": [{
"field": "EMAIL1",
"email": customerEmail,
// INSIDE the address object. Top level is the v1 shape: v2 ignores it,
// the opt-in never registers, and the address can land Non-Marketable.
"opt_in_reason": "Booked a consultation via Calendly"
}],
"custom_fields": fields
}
Finally, apply a tag. Not as a record of what happened, but as the thing that starts what happens next. A tag is an event other systems can subscribe to.
ONE DECISION WORTH GETTING RIGHT
Note Taker's email is full of chrome: icon captions, image links, an "Ask Notetaker" promo block on the end, and action items rendered as a lone bullet character on its own line with the text wrapped underneath it. Somewhere, someone has to clean that up.
The temptation is to clean it on the way in, so the CRM only ever holds tidy text. Do the opposite. Write it raw and clean it at the point of consumption.
Because cleaning is lossy and you cannot undo it. The CRM is where a human reads the recap, so it should be faithful to what was written. If you strip on the way in and your stripper is a little too greedy, you have destroyed the only copy, and you will not notice for months.
Clean it when you push it somewhere that needs it tidy. That code can be wrong and fixed. The CRM copy stays true.
WHAT YOU END UP WITH
- The call happens and nobody takes notes.
- Minutes later, the contact record has the summary, the action items, and the discussion on it, in fields you can merge into an email or read at a glance.
- A tag fires, and whatever comes next starts on its own.
- You did not touch anything.
COMMON QUESTIONS
This is the middle of a loop we run for our own clients: booking, recap, CRM, project created, nobody touching anything. If your meeting notes are dying in an inbox, that is a fixable week.
Book a Free Consult