ReelOp
18 February, 2026
ReelOp started from a simple creator-ops problem: Reel comments can become leads, support questions, feedback, or follow-up opportunities, but Instagram keeps that work inside a fast-moving comment surface. I wanted to turn that noise into a focused workspace.
The goal was not to build another analytics dashboard. The useful part was the daily workflow: pick a Reel, see the comments that need action, reply quickly, send private follow-ups where allowed, and keep track of what is already handled.
A desktop-first Instagram Business command center for working through high-volume Reel comments without losing the conversation state.

The Product Shape
I designed ReelOp like an inbox, not like a feed. The left side is for choosing the Reel, the center is for the comment queue, and the drawer is for acting on one comment without losing the list. That structure keeps the user inside the same operating rhythm.
The app supports searching, status filters, workflow sorting, keyboard selection, single replies, DMs, and bulk actions. It is intentionally desktop-only because the main use case is repeated work, not casual browsing.
Session And Token Safety
A big engineering decision was making the app useful before Instagram is connected, but still ready for real data after OAuth. The app checks whether Instagram env vars are configured, starts the Instagram authorization flow, validates the returned state, exchanges the code for an access token, upgrades it to a long-lived token when possible, and stores the session in an encrypted HTTP-only cookie.
After that, the product does not need a separate live dashboard. The same route handlers look for a valid session. If there is no session, they return preview data. If a session exists, they call Instagram Graph API and return live Reels, comments, replies, and DM action results to the same interface.
The useful part here is the session handling. Tokens are not stored in normal browser state. They are sealed with AES-GCM, placed inside an HTTP-only cookie, and refreshed near expiry so the workspace can keep syncing without asking the user to reconnect every time.
function sealPayload(payload) { const iv = crypto.randomBytes(12); const cipher = crypto.createCipheriv("aes-256-gcm", getSessionKey(), iv); const encrypted = Buffer.concat([ cipher.update(JSON.stringify(payload), "utf8"), cipher.final(), ]); const tag = cipher.getAuthTag(); return Buffer.concat([iv, tag, encrypted]).toString("base64url");}export function setSessionCookie(response, session) { const sealed = sealPayload(session); response.cookies.set(SESSION_COOKIE_NAME, sealed, getCookieOptions(SESSION_MAX_AGE_SECONDS), );}export async function getSessionWithRefresh() { const session = await getSessionFromCookies(); if (!session || !session.expiresAt) { return { session, refreshed: false }; } const expiresAtMs = new Date(session.expiresAt).getTime(); if (expiresAtMs - Date.now() > REFRESH_WINDOW_MS) { return { session, refreshed: false }; } try { const refreshed = await refreshLongLivedToken(session.accessToken); const nextSession = { ...session, accessToken: refreshed.access_token || session.accessToken, expiresAt: refreshed.expires_in ? new Date(Date.now() + refreshed.expires_in * 1000).toISOString() : session.expiresAt, tokenType: "long_lived", }; return { session: nextSession, refreshed: true }; } catch (error) { return { session, refreshed: false }; }}Graph API Layer
The Graph layer is defensive because Instagram responses are not always shaped the same across account types and permissions. ReelOp tries the me/media endpoint first, falls back to the connected Instagram user id when needed, filters media down to Reels or video posts, then pulls insights separately so a missing metric does not break the full sync.
Comments are paged per Reel with a limited number of concurrent requests. When Instagram allows reply fields, ReelOp uses them to mark existing replied comments. If that field is unsupported, it retries with a smaller field set and still keeps the inbox usable.
Bulk Work
The bulk flow is where the dashboard starts feeling useful. Users can select visible comments, shift-select a range, clear selection, or open a bulk drawer with a shared reply. During bulk reply, ReelOp tracks completed and failed items so the UI can stay honest instead of pretending every request succeeded.

The important part is not firing every request at once. Bulk reply uses a small worker pool, so the app can move through many selected comments without creating a sudden request spike or making progress state impossible to follow.
const handleBulkReply = useCallback(async (message) => { const targetIds = [...selectedCommentIds]; if (!targetIds.length) return; const progress = { active: true, type: "reply", total: targetIds.length, completed: 0, failed: 0, }; setBulkOperation(progress); await mapWithConcurrency(targetIds, 5, async (commentId) => { try { const existingComment = commentsById.get(commentId); const username = existingComment?.username || ""; await postReply(commentId, message, username); const nextReplyCount = Math.max( 1, toSafeNumber(existingComment?.replyCount) + (existingComment?.replied ? 0 : 1), ); updateCommentFlags([commentId], { replied: true, latestReplyText: buildReplyPreview(message, username), replyCount: nextReplyCount, }); clearCommentError(commentId); } catch (error) { progress.failed += 1; setCommentError(commentId, error.message || "Reply failed"); } finally { progress.completed += 1; setBulkOperation({ ...progress }); } }); setBulkOperation({ ...progress, active: false });}, [selectedCommentIds, commentsById, updateCommentFlags]);Single Comment Flow
For one-off replies, the drawer keeps the comment, context, reply box, and action history in the same place. ReelOp also prefixes the commenter's username when it makes sense, so replies feel native to Instagram instead of becoming generic canned messages.

Reply And DM State
ReelOp separates public replies from DM follow-ups because they are different jobs. A comment can be unreplied, replied, DM-only, or both replied and DM'd. That status becomes part of filtering, sorting, and progress tracking for each Reel.
The local store persists the working state, so a refresh does not wipe the user's sense of progress on the same browser. If the user closes the laptop and opens ReelOp again later, Zustand rehydrates the saved comments from localStorage, including the replied, dmSent, replyCount, and latestReplyText flags. The Instagram session itself lives separately in an encrypted HTTP-only cookie.
When live comments are synced again, ReelOp merges the fresh Instagram data with those local flags. That way, the product can pull new data from Graph API without casually erasing the workflow progress the user already created in the dashboard.

function mergeWithExistingCommentFlags(incomingComments, existingComments) { const existingById = new Map( existingComments.map((comment) => [comment.id, comment]), ); return incomingComments.map((comment) => { const existing = existingById.get(comment.id); if (!existing) return comment; return { ...comment, replied: existing.replied || comment.replied, dmSent: existing.dmSent, }; });}persist((set) => ({ updateCommentFlags: (commentIds, updates) => { set((state) => ({ comments: updateComments(state.comments, commentIds, updates), })); },}), { name: "reelop-store-v1", storage: createJSONStorage(() => localStorage), partialize: (state) => ({ reels: state.reels, comments: state.comments, dataSource: state.dataSource, connectedAccount: state.connectedAccount, theme: state.theme, }),});Settings Health Check
The Settings page is not just a place to disconnect. It also gives the user a quick connection health check. When someone clicks Check connection, ReelOp calls a debug route that verifies the local session, checks the Instagram profile endpoint, tries media fetches through both supported paths, and returns friendly recommendations when permissions, account type, or token validity look wrong.
That matters because most Instagram integration failures are not UI bugs. They are usually expired tokens, missing permissions, incorrect redirect URIs, or accounts that are not Business or Creator accounts. The health check turns those hidden API problems into clear product feedback.
Where It Is Now
ReelOp is a polished prototype with a real Instagram integration path: OAuth, encrypted session cookies, token refresh, Reel fetches, comment pagination, public replies, and private reply attempts. It can also run fully in preview mode, which makes the workflow easy to test without needing a connected account.
The project sits at the intersection I like working in: product workflow, interaction design, and implementation details that make the experience feel reliable when the volume gets high.
Try it here: reelop.vercel.app