I built a real-time inbox with Supabase Realtime to replace Slack for agencies

I spent a year building Kobin — an agency operating system that replaces Slack, Notion, HubSpot, Linear, and Buffer. The inbox was the hardest part to build. Here’s how it works technically.

The architecture

The inbox uses Supabase Realtime WebSocket channels. Each room (project room, group chat, DM) gets its own channel subscription. Switching rooms tears down the old channel and creates a new one — this keeps memory clean and avoids stale message states.

Messages load the last 20 first. Older messages load on scroll-up demand with no flash to top. An animated skeleton UI shows while rooms are loading.

The features that were harder than expected

Replies — storing the quoted message as a nested reference without blowing up query complexity. We denormalize the preview text at write time.

Unread badges — tracked via last_read_at timestamp per user per room. Simple in theory, surprisingly tricky when you have real-time updates coming in while the user is actively reading.

@ai mentions — when a message contains @AI, the server assembles a full context payload (project status, last 20 messages, open tasks, upcoming meetings, vault items) and sends it to the model. The response streams back as a message in the thread with a purple avatar.

File attachments — images open in a full-screen lightbox with download. Office docs and PDFs get a preview card. ZIPs show a file icon with size.

Why I built it instead of using Slack

Slack’s AI sees messages. Asana’s AI sees tasks. Notion’s AI sees documents. None of them see the full picture.

When everything — messages, tasks, files, meetings, CRM contacts — lives in the same schema, the AI actually has something to work with. That’s the whole point of building the inbox as part of a unified workspace rather than integrating with Slack.

Performance

Room list loads with 5 total queries regardless of how many rooms exist. This took several iterations to get right with Supabase RLS policies in play.

The full product

The inbox is one of 8 modules in Kobin. The others: Vault (Google Drive-backed), Tasks, CRM, Client Portal, Calendar, LinkedIn Studio, and an AI layer in development.

Full writeup on the problem it solves (with data): https://www.kobin.team/blog/the-283-problem

Waitlist if you run an agency: https://www.kobin.team

Happy to go deeper on any of the technical decisions in the comments.

Vercel AI SDK Has a Free API: Build Full-Stack AI Apps in Minutes

What is Vercel AI SDK?

Vercel AI SDK is an open-source TypeScript toolkit for building AI-powered applications. It provides a unified API to work with any LLM provider (OpenAI, Anthropic, Google, Mistral, Groq) and React hooks for streaming AI responses in your frontend.

Why Vercel AI SDK?

  • Free and open-source — MIT license, no vendor lock-in
  • Unified API — same code works with OpenAI, Anthropic, Google, Mistral, Groq
  • Streaming built-in — real-time AI responses with React hooks
  • Structured output — type-safe AI responses with Zod schemas
  • Tool calling — unified tool/function calling across all providers
  • Next.js optimized — seamless integration with App Router

Quick Start

npx create-next-app@latest my-ai-app
cd my-ai-app
npm install ai @ai-sdk/openai

Server-Side: Route Handler

// app/api/chat/route.ts
import { openai } from '@ai-sdk/openai';
import { streamText } from 'ai';

export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = streamText({
    model: openai('gpt-4-turbo'),
    messages,
    system: 'You are a helpful DevOps assistant.',
  });

  return result.toDataStreamResponse();
}

Client-Side: Chat UI

// app/page.tsx
'use client';
import { useChat } from 'ai/react';

export default function Chat() {
  const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat();

  return (
    <div className="max-w-2xl mx-auto p-4">
      {messages.map(m => (
        <div key={m.id} className={m.role === 'user' ? 'text-blue-600' : 'text-gray-800'}>
          <strong>{m.role}:</strong> {m.content}
        </div>
      ))}
      <form onSubmit={handleSubmit}>
        <input
          value={input}
          onChange={handleInputChange}
          placeholder="Ask about DevOps..."
          className="w-full p-2 border rounded"
        />
      </form>
    </div>
  );
}

Switch Providers with One Line

import { openai } from '@ai-sdk/openai';
import { anthropic } from '@ai-sdk/anthropic';
import { google } from '@ai-sdk/google';
import { mistral } from '@ai-sdk/mistral';

// Same code, different provider
const result = streamText({
  model: anthropic('claude-3-5-sonnet-20241022'),
  // model: google('gemini-1.5-pro'),
  // model: mistral('mistral-large-latest'),
  messages,
});

Structured Output with Zod

import { generateObject } from 'ai';
import { z } from 'zod';

const { object } = await generateObject({
  model: openai('gpt-4-turbo'),
  schema: z.object({
    name: z.string(),
    pros: z.array(z.string()),
    cons: z.array(z.string()),
    rating: z.number().min(1).max(10),
    recommendation: z.string(),
  }),
  prompt: 'Review ArgoCD for GitOps deployments',
});

// object is fully typed!
console.log(object.pros); // string[]

Tool Calling

import { streamText, tool } from 'ai';
import { z } from 'zod';

const result = streamText({
  model: openai('gpt-4-turbo'),
  messages,
  tools: {
    getWeather: tool({
      description: 'Get weather for a city',
      parameters: z.object({ city: z.string() }),
      execute: async ({ city }) => {
        const res = await fetch(`https://api.weather.com/${city}`);
        return res.json();
      },
    }),
    searchDocs: tool({
      description: 'Search documentation',
      parameters: z.object({ query: z.string() }),
      execute: async ({ query }) => {
        return searchIndex(query);
      },
    }),
  },
});

Vercel AI SDK vs Alternatives

Feature Vercel AI SDK LangChain.js OpenAI SDK
Multi-provider Yes Yes OpenAI only
React hooks Built-in None None
Streaming Native Manual Manual
Type safety Full TypeScript Partial Good
Structured output Zod schemas Parsers JSON mode
Bundle size ~15KB ~200KB+ ~50KB

Real-World Impact

A SaaS startup built their AI chat feature with raw OpenAI SDK. Switching to Anthropic for better reasoning required rewriting the entire integration. After migrating to Vercel AI SDK: they now A/B test between 4 providers by changing one line. Development time for new AI features dropped from weeks to days.

Building AI-powered web apps? I help teams ship production AI features fast. Contact [email protected] or explore my data tools on Apify.

Edirne'de Astro Hackathon heyecanı başladı

Sanayi ve Teknoloji Bakanlığı’nın koordinasyonunda Türkiye Uzay Ajansı tarafından düzenlenen Astro Hackathon’un Edirne etabı, Trakya Üniversitesi’nde başladı. Lise ve üniversite öğrencilerinden oluşan ekipler, uzay ve teknoloji alanında projeler geliştirerek yarın jüriye sunacak.

Başkentte, TÜBİTAK Bilim Şenliği düzenlendi

Altındağ Belediyesi ve İlçe Milli Eğitim Müdürlüğü tarafından düzenlenen ‘TÜBİTAK 4007 Bilim Şenliği’, Yunus Emre Kültür Merkezi’nde gerçekleştirildi. Etkinlikte öğrencilerin hazırladığı bilimsel deneyler, teknoloji sergileri ve atölye çalışmaları ile katılımcılara farklı öğrenme deneyimleri sunuldu.

Zelenskiy, Rusya'nın Odessa kentine 60'tan fazla İHA ile saldırdığını söyledi

Ukrayna Devlet Başkanı Zelenskiy, Rus ordusunun Odessa’ya yönelik düzenlediği 60’tan fazla silahlı insansız hava aracı (İHA) saldırısını kınayarak, bunun sivillere karşı bir terör eylemi olduğunu ifade etti. Saldırıda bir doğum hastanesi ve kritik altyapılar hasar gördü, bir kişi hayatını kaybetti, 10’dan fazla kişi yaralandı.