A meeting recap template you send as a link

Decisions first, then actions with one owner and one date each. Have an AI write it as a single web page from your notes, then post the link in Slack or Teams.

A meeting recap template needs four parts: a two-sentence summary, the decisions, the actions with one owner and one date each, and the open questions. Put them on one web page and post the link in Slack or Teams.

The channel then holds one recap, not a pile of files.

Here is a finished recap you can copy. Tick an action off, or show only one person's actions. An AI chat can write the same page from your notes.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Recap: Pricing page kickoff</title>
<style>
  * { box-sizing: border-box; }
  body { margin: 0; padding: 16px; font-family: system-ui, "Segoe UI", sans-serif; background: #f3f4f6; color: #1d2330; line-height: 1.45; }
  .page { max-width: 640px; margin: 0 auto; background: #fff; border-radius: 14px; padding: 20px 20px 22px; box-shadow: 0 2px 10px rgba(0,0,0,.07); }
  .kicker { font-size: 12px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: #6b7280; }
  h1 { font-size: 22px; line-height: 1.2; margin: 4px 0 6px; }
  .meta { font-size: 14px; color: #4b5563; margin: 0 0 14px; }
  .sum { background: #eef4ff; border-left: 4px solid #2563eb; border-radius: 6px; padding: 10px 12px; font-size: 15px; margin: 0 0 18px; }
  h2 { font-size: 13px; letter-spacing: .06em; text-transform: uppercase; color: #374151; margin: 20px 0 8px; }
  ol.dec { margin: 0; padding: 0; list-style: none; counter-reset: d; }
  ol.dec li { counter-increment: d; position: relative; padding: 8px 0 8px 34px; border-top: 1px solid #eceff3; font-size: 15px; }
  ol.dec li:first-child { border-top: 0; }
  ol.dec li::before { content: counter(d); position: absolute; left: 0; top: 8px; width: 24px; height: 24px; border-radius: 50%; background: #166534; color: #fff; font-size: 13px; font-weight: 700; display: grid; place-items: center; }
  ol.dec small { display: block; color: #6b7280; font-size: 13px; }
  .chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 8px; }
  .chips button { font: inherit; font-size: 13px; padding: 5px 11px; border-radius: 999px; border: 1px solid #cbd2dc; background: #fff; color: #1d2330; cursor: pointer; }
  .chips button[aria-pressed="true"] { background: #1d2330; border-color: #1d2330; color: #fff; }
  ul.act { margin: 0; padding: 0; list-style: none; }
  ul.act li { display: flex; align-items: flex-start; gap: 10px; padding: 9px 0; border-top: 1px solid #eceff3; }
  ul.act li:first-child { border-top: 0; }
  ul.act input { width: 20px; height: 20px; margin: 2px 0 0; flex: none; }
  ul.act .t { flex: 1; font-size: 15px; min-width: 0; }
  ul.act .who { display: block; font-size: 13px; color: #4b5563; margin-top: 2px; }
  ul.act .who b { color: #1d2330; }
  ul.act li.done .t { color: #8a93a1; text-decoration: line-through; }
  ul.act li[hidden] { display: none; }
  .count { font-size: 13px; color: #4b5563; margin: 8px 0 0; }
  .open { background: #fff7ed; border: 1px solid #fed7aa; border-radius: 8px; padding: 10px 12px; font-size: 15px; margin: 0; }
</style>
</head>
<body>
<div class="page">
  <div class="kicker">Meeting recap</div>
  <h1>Pricing page kickoff</h1>
  <p class="meta">Thu 25 Sep 2026 &middot; 30 min &middot; Maya, Tomas, Priya, Daniel</p>

  <p class="sum"><b>In short:</b> the pricing page launches on 14 Oct instead of 7 Oct, because the legal review needs one more week. Nothing else changes.</p>

  <h2>Decisions</h2>
  <ol class="dec">
    <li>Launch the new pricing page on 14 Oct, not 7 Oct.<small>Why: legal copy review needs one more week.</small></li>
    <li>Keep the current logo on the page.<small>The logo refresh is a separate project.</small></li>
    <li>The weekly check-in moves to Tuesdays at 10:00.</li>
  </ol>

  <h2>Actions</h2>
  <div class="chips" id="chips">
    <button type="button" data-who="all" aria-pressed="true">Everyone</button>
    <button type="button" data-who="Maya" aria-pressed="false">Maya</button>
    <button type="button" data-who="Tomas" aria-pressed="false">Tomas</button>
    <button type="button" data-who="Priya" aria-pressed="false">Priya</button>
    <button type="button" data-who="Daniel" aria-pressed="false">Daniel</button>
  </div>
  <ul class="act" id="act">
    <li data-who="Maya"><input type="checkbox" aria-label="Done"><span class="t">Send the final pricing copy to legal<span class="who"><b>Maya</b> &middot; due 3 Oct</span></span></li>
    <li data-who="Priya"><input type="checkbox" aria-label="Done"><span class="t">Book the Tuesday 10:00 slot<span class="who"><b>Priya</b> &middot; due 2 Oct</span></span></li>
    <li data-who="Tomas"><input type="checkbox" aria-label="Done"><span class="t">Update the plan comparison table<span class="who"><b>Tomas</b> &middot; due 8 Oct</span></span></li>
    <li data-who="Daniel"><input type="checkbox" aria-label="Done"><span class="t">Share the staging link with the support team<span class="who"><b>Daniel</b> &middot; due 9 Oct</span></span></li>
  </ul>
  <p class="count" id="count"></p>

  <h2>Open question</h2>
  <p class="open">Who approves the wording of the annual-plan discount? <b>No owner yet.</b> Raise it on Tuesday.</p>
</div>

<script>
  const chips = document.querySelectorAll('#chips button');
  const rows = document.querySelectorAll('#act li');
  const count = document.getElementById('count');

  function update() {
    const done = document.querySelectorAll('#act input:checked').length;
    count.textContent = done + ' of ' + rows.length + ' actions done';
  }

  // Show only one person's actions
  chips.forEach((chip) => {
    chip.addEventListener('click', () => {
      chips.forEach((c) => c.setAttribute('aria-pressed', c === chip));
      rows.forEach((row) => {
        row.hidden = chip.dataset.who !== 'all' && row.dataset.who !== chip.dataset.who;
      });
    });
  });

  // Tick an action as done
  rows.forEach((row) => {
    row.querySelector('input').addEventListener('change', (e) => {
      row.classList.toggle('done', e.target.checked);
      update();
    });
  });

  update();
</script>
</body>
</html>
A finished recap. Tick an action off, or show one person's actions only. Edit the code and the page reruns.

The order is on purpose. Decisions and actions come before any discussion, because that is what people open a recap to find. How to host meeting notes explains the reasoning in more detail.

Make it with AI

Paste this into ChatGPT, Claude or Gemini and put your notes after the last line. Rough notes are enough.

Turn my meeting notes into ONE self-contained HTML file: a meeting recap page.

Order: title and date, a two-sentence summary, Decisions (numbered,
each with one line of why), Actions, Open questions.

Actions: one line each, with ONE owner's name, a due date and a
checkbox. If the notes give no owner or date, write "No owner yet"
or "No date yet". Do not guess.

Use only what is in the notes. Do not add anything.
Plain HTML, CSS and JavaScript in the one file. No outside images,
fonts or scripts. Large readable text, good on a phone.

Notes:
[paste your notes here]

Before you use the result, check these:

  • Every action has one name and one date. Where the notes had none, the page should say so.
  • Each decision is a whole sentence that makes sense to someone who was not there.
  • Names, dates and numbers match your notes. You wrote the notes, so you check the page.
  • Nothing appears that nobody said in the meeting.
  • The page has no outside images and no form that sends data.

If the notes are confidential, check your organisation's rules before pasting them into an AI tool. Sharing ChatGPT's HTML output covers the ChatGPT side of copying the result.

Or start from the blank version

No AI, or a meeting too short to bother? Copy this blank page, replace each highlighted placeholder, and add rows to the actions list as needed.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Blank meeting recap</title>
<style>
  * { box-sizing: border-box; }
  body { margin: 0; padding: 16px; font-family: system-ui, "Segoe UI", sans-serif; background: #f3f4f6; color: #1d2330; line-height: 1.45; }
  .page { max-width: 640px; margin: 0 auto; background: #fff; border-radius: 14px; padding: 20px 20px 22px; box-shadow: 0 2px 10px rgba(0,0,0,.07); }
  .kicker { font-size: 12px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: #6b7280; }
  h1 { font-size: 22px; line-height: 1.2; margin: 4px 0 6px; }
  .meta { font-size: 14px; color: #4b5563; margin: 0 0 14px; }
  h2 { font-size: 13px; letter-spacing: .06em; text-transform: uppercase; color: #374151; margin: 20px 0 8px; }
  .ph { background: #fef3c7; border-radius: 4px; padding: 0 4px; color: #78350f; }
  .box { border: 1px dashed #cbd2dc; border-radius: 8px; padding: 10px 12px; font-size: 15px; margin: 0 0 8px; }
  .row { display: grid; grid-template-columns: 1fr auto auto; gap: 8px 12px; align-items: center; padding: 9px 0; border-top: 1px solid #eceff3; font-size: 15px; }
  .row:first-of-type { border-top: 0; }
  .row .o, .row .d { font-size: 14px; white-space: nowrap; }
  .head { font-size: 12px; color: #6b7280; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; }
  button { font: inherit; font-size: 14px; padding: 8px 14px; border-radius: 8px; border: 1px solid #1d2330; background: #1d2330; color: #fff; cursor: pointer; margin-top: 10px; }
  .hint { font-size: 13px; color: #6b7280; margin: 14px 0 0; }
  .row .ph { justify-self: start; }
  @media (max-width: 460px) { .row { grid-template-columns: 1fr; gap: 2px; } .row.head { display: none; } }
</style>
</head>
<body>
<div class="page">
  <div class="kicker">Meeting recap</div>
  <h1><span class="ph">Meeting name</span></h1>
  <p class="meta"><span class="ph">Date</span> &middot; <span class="ph">Length</span> &middot; <span class="ph">Who was there</span></p>

  <h2>In short</h2>
  <p class="box"><span class="ph">One or two sentences: what changed because of this meeting.</span></p>

  <h2>Decisions</h2>
  <p class="box">1. <span class="ph">What was decided, in a full sentence.</span> Why: <span class="ph">one line.</span></p>
  <p class="box">2. <span class="ph">What was decided.</span></p>

  <h2>Actions</h2>
  <div id="rows">
    <div class="row head"><span>Action</span><span>Owner</span><span>Due</span></div>
    <div class="row"><span class="ph">What will be done</span><span class="o ph">Name</span><span class="d ph">Date</span></div>
  </div>
  <button type="button" id="add">+ Add an action row</button>

  <h2>Open questions</h2>
  <p class="box"><span class="ph">Anything not settled, and who will settle it.</span></p>

  <p class="hint">Replace each highlighted placeholder. Every action needs one person and one date.</p>
</div>

<script>
  const rows = document.getElementById('rows');

  // Add one more empty action row
  document.getElementById('add').addEventListener('click', () => {
    const row = document.createElement('div');
    row.className = 'row';
    row.innerHTML = '<span class="ph">What will be done</span><span class="o ph">Name</span><span class="d ph">Date</span>';
    rows.appendChild(row);
  });
</script>
</body>
</html>
A blank recap with highlighted placeholders and a button that adds an action row.

Each action row has three slots: what, who and when. A row with no name is not an action yet. Move it to open questions and say who will settle it.

  1. Paste the page into a NOS document. Paste the HTML the AI gave you, or the blank template once you have filled it in.
  2. Choose Create share link and copy the link. HTML to link walks through it.
  3. Post the link in the channel with one line saying what it is and the main decision in words.
  4. Fix mistakes in the page. Edit the code in NOS. The link you posted shows the new version.
From notes to a posted link in four steps.
From notes to a posted link in four steps.

Whoever opens the link sees the page in a normal browser tab, on a laptop or a phone, without an account. They can read it but cannot edit it. The scripts in the page run, so the filter buttons work for them too.

A tick you make in NOS's View mode is saved. A tick a visitor makes is not. So the saved state of the actions is yours.

The same link in the chat, then one tap to the page on a phone. Illustration.
The same link in the chat, then one tap to the page on a phone. Illustration.

Anyone with the link can open the page, and there is no password. Leave out anything that should not leave the room, and post the link only where the right people are.

Slack shows a preview of a shared link just below the message, and each person can switch link previews off in their own preferences. In Teams, an admin can turn URL previews on or off in a messaging policy.

Both work this way as of October 2026.

So do not rely on the card. Write the link after one line such as "Recap of today: launch moves to 14 Oct. Actions with owners are on the page." The message then makes sense even with no preview.

In Teams, you can also select Format under the message box and insert the link with your own display text. The file route is covered in sending an HTML file in Slack and in Teams.

A recap sent as a file tends to multiply. One agency's write-up of a client project describes it like this: "By presentation day there were four versions of the file floating around Slack and no one could say with certainty which one was final."

Four uploaded files versus one link that is edited in place.
Four uploaded files versus one link that is edited in place.
Way to send it A correction takes Which version is current
Text pasted into the message Editing the message or posting another The latest message, if people scroll that far
HTML file attached Uploading a new file Unclear, because every upload stays in the channel
Link to a NOS page Editing the page The same link always shows the current version

A link is also just text, so the same one fits in an email follow-up or a project page. Why links beat attachments goes further.

When it does not work

What you see Cause Fix
No preview card under the link Previews can be switched off for a person (Slack) or an organisation (Teams) Say what the link is in the message text
A tick on the page does not stay for other people A visitor's changes are not saved; the owner's View mode changes are Treat the page as read-only for others; track progress in the thread
An image is missing Images loaded from other sites are blocked Ask the AI for no outside images; use shapes drawn in the page
A "send comments" form does nothing Sending a form to a server is blocked Collect replies in the thread
The text looks plain, the font is wrong A font or script came from a site that is not allowed Use Google Fonts, cdnjs, jsDelivr, unpkg, or none
A name or date on the page is wrong The AI misread the notes, or the notes were unclear Correct it in NOS. The same link updates
Someone outside the project opened it Anyone with the link can open it Keep sensitive items out and post only where intended

Questions people ask

What should a meeting recap include?

A two-sentence summary, the decisions, the actions, and the open questions. Each action needs one owner and one date. If nobody owns an item yet, list it under open questions instead of giving it a vague owner such as "the team".

Can AI write the recap?

Yes. Paste your notes and the prompt from this page into ChatGPT, Claude or Gemini and ask for one HTML file. You still check every name, date and number against your own notes before you share it.

Does the person who opens the link need an account?

No. A NOS share link opens in a normal browser tab, on a laptop or a phone, for anyone who has the link. They can read the page but cannot edit it.

What if a decision changes after I posted the link?

Edit the code in NOS. The link you already posted shows the new version, so there is nothing to re-upload. It helps to add a short "Updated" line at the top so readers know the page changed.

Can people reply on the page?

Not on the page itself. Visitors cannot edit it, and a form that sends data to a server is blocked. Keep the discussion in the thread under your Slack or Teams message and put the link there.

Keep reading