Inspiration
I built Margin because I already use Slack to take quick notes during meetings. Sending myself one sentence is faster than opening another app, but those messages usually get buried and lose the context of what meeting they came from.
I did not want to build another meeting assistant that records or transcribes the entire call. Most of the time, I only care about a few specific things: a question I need to ask later, a decision that was made, or an action item I cannot forget.
Margin is built around that workflow. The user decides what is worth saving, and Margin helps organize it and bring it back later.
What it does
Margin is a private meeting-memory agent for Slack.
During a meeting or huddle, the user sends Margin a short DM such as:
Ask whether the migration affects customer-created workflows.
Margin saves the exact original message before making any external API or AI request. It can then:
- organize and classify the note;
- check nearby Google Calendar events and Slack huddle context;
- connect the note to a meeting when the evidence is strong enough;
- ask the user when the meeting context is unclear;
- keep the original text separate from the AI-organized version;
- send private post-meeting summaries;
- resurface open questions and action items before related meetings;
- search across the user's saved notes; and
- create and deliver reminders through Slack.
Margin also exposes notes and reminders through MCP. This allows an existing MCP-compatible assistant to search Margin notes, save exact memories, find notes that need review, and create or cancel reminders.
Margin does not record meetings, create transcripts, read unrelated Slack conversations, or search the user's full message history.
How I built it
Margin is written in TypeScript using Node.js and Slack Bolt.
Slack Agent View and private DMs are used for note capture. Block Kit is used for note cards, buttons, modals, meeting clarification, reminders, and editing. Socket Mode handles Slack events, and App Home is used for setup and private data controls.
PostgreSQL stores the original notes, organized versions, revisions, meeting context, reminders, scheduled jobs, preferences, and encrypted Google OAuth credentials.
The original note is saved before any AI or Calendar processing. Database constraints keep the original text immutable and prevent duplicate Slack events from creating duplicate notes.
Google Calendar is optional and uses read-only event access. Margin only checks limited time windows around note capture and upcoming meetings. Related recurring meetings are matched using the actual Calendar series identifier instead of only comparing titles.
OpenAI Structured Outputs are used for optional note organization and classification. Responses must pass a strict Zod schema. If the model fails, refuses, or returns invalid output, Margin falls back to the original note that was already saved.
I also built a separate MCP server with owner-scoped tools for searching notes, capturing exact text, reviewing uncertain notes, and managing reminders.
The MCP server does not receive Slack credentials. The main Margin application handles storage, scheduling, retries, and private Slack delivery.
PostgreSQL-backed workers handle reminders, post-meeting summaries, recurring-meeting resurfacing, retries, and recovery after restarts.
Challenges
The hardest problem was figuring out which meeting a note belonged to.
A note may be written while the user is in a Slack huddle, while a Calendar event is active, or while several nearby meetings are possible. These signals can disagree.
I did not want Margin to silently attach a private note to the wrong meeting, so I built a deterministic scoring system. Margin records the evidence behind each option and only selects a meeting automatically when one is clearly strong enough. Otherwise, it asks the user to choose.
The other major challenge was reliability.
Slack can retry events, APIs can fail, AI output can be invalid, and the app can restart while a reminder is being delivered. Margin handles this by saving the original first, using database-level idempotency, validating AI output, storing scheduled work in PostgreSQL, and recovering unfinished jobs after restarts.
The MCP integration also needed strict boundaries. Notes and reminders are limited to the current owner, Slack source information must come from trusted context, and ambiguous reminder times should be clarified instead of guessed.
What I learned
The main thing I learned is that an AI agent does not need broad access to be useful.
Margin does not need a full recording, transcript, or access to every Slack conversation. It only needs the exact information the user deliberately saves and a small amount of verified meeting context.
I also learned that uncertainty should be shown instead of hidden. Asking one direct question is better than confidently saving the wrong context.
Another important decision was keeping the original user text separate from every AI-generated version. AI can help organize and retrieve information, but it should not replace the source of truth.
The MCP integration also helped separate the responsibilities of the system. The host assistant handles language and reasoning. Margin handles private storage, ownership, provenance, scheduling, retries, and Slack delivery.
What's next
The next step is a better App Home dashboard for recent notes, open actions, unresolved questions, reminders, and notes that still need review.
I would also like to add secure remote MCP access, date-range search, better feedback when a note is organized incorrectly, more complete end-to-end Slack testing, and stronger backup and migration testing.
Margin is intentionally focused. It is not meant to replace a task manager or store an entire meeting. It is meant to make sure the specific things the user chose to remember do not get lost.
Built With
- block-kit
- docker
- docker-compose
- google-calendar-api
- model-context-protocol-(mcp)
- node.js
- openai-responses-api
- postgresql
- slack-agent-view
- slack-app-home
- slack-bolt
- slack-events-api
- slack-web-api
- socket-mode
- structured-outputs
- typescript
- zod
Log in or sign up for Devpost to join the conversation.