Plan events with people who will actually get along.
Series Events is a hackathon project built on the Series iMessage API. It scores the group chemistry of a guest list from messaging metadata, and lets you create and manage events by talking to an AI assistant called Vibe, either over iMessage or in a web chat. Message content is never stored. Only timestamps, lengths and hashed IDs are used.
- Chemistry prediction. Each person gets a communication profile: response time, active hours, message frequency, group participation, verbosity, and social-graph metrics such as betweenness centrality and clustering.
ChemistryPredictorcombines pairwise compatibility, energy balance and catalyst or dominator counts into a group score with insights and warnings. It can also suggest who to add or remove. - Conversational event management. The Vibe agent classifies a message into
create_event,rsvp,update_event,cancel_eventorqueryusing Claude, and falls back to keyword matching when no Anthropic key is set. It creates the event, sends invitations, and replies in natural language. - iMessage in and out. A poller checks the Series API every 3 seconds for new messages, so no webhook is needed. Replies, invitations, reminders and group-chat messages go back out through the same API. Only the two demo phone numbers are processed. Everyone else is ignored.
- Web chat. The frontend has a
/chatpage that talks to the same agent overPOST /api/chat, with history kept in memory on the server. - Kafka profile building. With Series Kafka credentials, the backend consumes the team topic, strips each message down to metadata with
PrivacyGuard, and updates profiles and the social graph in batches. Without credentials it generates mock messages and builds profiles from those. - Privacy endpoints.
GET /api/privacy/export/:userIdandDELETE /api/privacy/delete/:userIdlet a user export or remove their profile.
flowchart LR
Phone["Demo phone<br/>iMessage"]
Web["Frontend<br/>React + Vite"]
Series["Series API"]
Kafka["Series Kafka topic"]
Poller["MessagePoller<br/>every 3 s"]
Vibe["VibeAIAgent<br/>Claude or keyword fallback"]
Events["EventService +<br/>InvitationService"]
Consumer["KafkaMessageConsumer<br/>+ PrivacyGuard"]
Profiles["ProfileBuilder +<br/>GraphAnalyzer"]
Chem["ChemistryPredictor"]
Phone <--> Series
Series --> Poller --> Vibe
Web -- "REST /api" --> Vibe
Web -- "REST /api" --> Chem
Vibe --> Events --> Series
Kafka --> Consumer --> Profiles --> Chem
The backend is a single Express process. On start it loads Series credentials from the environment, connects to Kafka if it can, builds profiles (real or mock), then starts the poller and the HTTP API on port 3001. Everything lives in memory. Restarting the server drops all events, profiles and chat history.
More detail is in CONVERSATIONAL_EVENTS.md, KAFKA_MESSAGE_PROCESSING.md, WEB_CHAT_IMPLEMENTATION.md and DEMO_GUIDE.md.
You need Node 18+ and npm. Series and Anthropic credentials are optional. Without them the backend runs in mock mode and the agent uses keyword matching.
1. Backend
cd backend
npm install
npm run dev # http://localhost:3001/api2. Frontend
cd frontend
echo "VITE_API_URL=http://localhost:3001/api" > .env
npm install
npm run dev # http://localhost:8080The .env line is required. The chat page reads VITE_API_URL with no fallback.
3. Log in
Go to /login and use one of the demo users with password 123:
- Sarah Kim,
+14843693839 - Alex Thompson,
+19178615579
Then open /chat and try "Create dinner Friday at 7pm with Sarah" or "What events do I have?".
iMessage instead of web chat
Set the Series variables below and text the sender number from one of the demo phones. The poller picks the message up and the agent replies over iMessage.
All settings are environment variables. The backend loads backend/.env through dotenv.
| Variable | Used by | Purpose |
|---|---|---|
PORT |
backend | HTTP port (default 3001). |
SERIES_API_KEY, SERIES_SENDER_PHONE |
backend | Series iMessage API key and the E.164 number the bot sends from. Both are needed for polling and outbound messages. |
SERIES_API_BASE_URL |
backend | Series API base URL. Defaults to the hackathon service. |
USE_SERIES_API |
backend | Set to true to connect to Series Kafka. Ignored if the Kafka variables are missing. |
SERIES_KAFKA_BROKERS, SERIES_KAFKA_TOPIC, SERIES_KAFKA_GROUP_ID, SERIES_KAFKA_CLIENT_ID |
backend | Confluent Cloud connection. All four are required for Kafka. Brokers are comma separated. |
SERIES_KAFKA_SASL_USERNAME, SERIES_KAFKA_SASL_PASSWORD |
backend | Enables SASL_SSL with PLAIN. Without them the consumer uses PLAINTEXT. |
ANTHROPIC_API_KEY |
backend | Enables Claude for intent classification. Unset falls back to keyword matching. |
HASH_SALT |
backend | Salt for hashing user and conversation IDs (default series-events-salt). |
FRONTEND_URL |
backend | Allowed CORS origin. |
LOG_LEVEL, NODE_ENV |
backend | Winston log level and environment. |
VITE_API_URL |
frontend | Backend API base, e.g. http://localhost:3001/api. Required for the chat page. |
VITE_USE_BACKEND |
frontend | Set to false to compute chemistry in the browser instead of calling the backend. |
API_URL, TEST_PHONE |
scripts | Used by scripts/test-invite.ts and scripts/test-imessage.ts. |
Generic KAFKA_* variables are not read. The backend only accepts SERIES_KAFKA_* so it cannot connect to the wrong topic.
backend/ Express + TypeScript API
├── src/
│ ├── index.ts boots services, Kafka, the poller and the HTTP server
│ ├── api/ routes.ts and middleware.ts
│ ├── models/ Event, CommunicationProfile, ChemistryAnalysis, DemoProfiles, SeriesConfig
│ ├── services/ chemistryPredictor, profileBuilder, graphAnalyzer, eventService,
│ │ invitationService, inboundMessageHandler (Vibe), messagePoller,
│ │ kafkaConsumer, chatMessageStore
│ └── utils/ privacy.ts (PrivacyGuard), calculations, logger, mockDataGenerator
├── scripts/ demo.ts, test-imessage.ts, test-invite.ts, test-message-poller.ts
├── tests/ vitest unit tests for ChemistryPredictor
├── Dockerfile, docker-compose.yml
└── README.md, SERIES_SETUP.md, RATE_LIMITS.md
frontend/ React + Vite + Tailwind + shadcn/ui
└── src/pages/ Login, Chat, CreateEvent, GuestListBuilder, EventDashboard,
EventDetail, EditEvent, Privacy
ai-social-finder/ Separate Lovable-generated UI prototype with mock profiles. Not wired to the backend.
All routes are under /api. The frontend uses the -frontend variants, which accept and return the shapes in models/FrontendModels.ts.
| Route | Purpose |
|---|---|
GET /health |
Liveness |
GET /profile/:userId, GET /profiles |
Communication profiles |
POST /chemistry/predict, POST /chemistry/optimize, POST /chemistry/individual |
Group score, guest-list optimisation, single-person analysis |
POST /events, GET /events/:eventId, GET /events/user/:userId, POST /events/:eventId/invite |
Events |
POST /events-frontend, GET, PUT, DELETE /events-frontend/:eventId |
Events for the web UI |
PUT /events-frontend/:eventId/rsvp, POST .../invite, .../reminders, .../group-chat |
Guest actions and outbound iMessages |
POST /chat, GET /chat/history/:userId |
Web chat with the Vibe agent |
POST /inbound-message |
Manual inbound message hook, used by test scripts |
GET /privacy/export/:userId, DELETE /privacy/delete/:userId |
Data export and deletion |
cd backend
npm test # vitest
npm run demo # scripted demo against the chemistry engine
npx tsx scripts/test-imessage.ts +1555... # send a test iMessage
npx tsx scripts/test-message-poller.ts # exercise the poller
npm run build && npm start # compiled build
cd frontend
npm run lint && npm run buildbackend/docker-compose.yml builds the backend image and exposes port 3001, but it passes generic KAFKA_* variables that the code does not read. Add the SERIES_* variables to the compose environment before using it.
- This is a hackathon build. There is no database. Events, profiles, chat history and RSVPs are lost on restart.
- Only two hard-coded demo users can log in or message the bot. The password is
123and is checked in the browser. - Chemistry scores come from heuristics over message metadata, not a trained model. Without Kafka the profiles are generated from random mock data.
- Series API rate limits apply to outbound messages. See backend/RATE_LIMITS.md.
ai-social-finder/is an unrelated UI experiment kept in the repo.
MIT