Menu
Back to writing

Building

I tried making Instagram DMs the login screen

I built an Omni experiment where sending an Instagram DM bootstrapped a guest account and started conversational onboarding. Removing signup friction was easy. Losing control of conversation boundaries was the interesting part.

I tried making Instagram DMs the login screen

Most software starts with an obstacle course.

You find an app you want to try. First, you get dropped on a landing page. Then you click a button to create an account. You enter an email, create a password, wait for a six-digit verification code in your inbox, click a link, choose a username, accept the terms, and click through a three-step onboarding tour.

Only after you finish all of that do you finally get to see whether the thing is actually useful.

For an early product experiment, that friction is lethal.

Back in early 2026, while experimenting with different distribution loops for Omni, I wanted to see what happened if I removed that entire onboarding sequence.

The idea was simple:

What if you could discover Omni on Instagram, send a direct message to @omnisocialapp, and immediately start talking to the AI?

No signup page. No password. No app download. No email verification.

The message itself would become the registration, the onboarding, and the interface.

I recorded a short demo of the flow while testing it:

At first, this felt like an unusually elegant way to eliminate onboarding friction.

Then I ran into a problem I had mostly taken for granted when building my own apps:

Instagram gave me the conversation.

It did not give me my own conversation model.

How the DM became the account bootstrap

The technical mechanism behind this relied on Meta's Instagram Messaging API and webhook events.

When a person sends a message to an Instagram business or creator account, Meta's Graph API sends a webhook payload to your server. That event includes an Instagram-Scoped User ID (often referred to as an IGSID or scoped ID).

That scoped ID is unique to the interaction between that specific Instagram user and your app.

Here is how the backend handled the message:

Instagram DM sent to @omnisocialapp


Meta Webhook event received


Check database for Instagram Scoped ID
       ╱   ╲
     yes    no
     ╱        ╲
    ▼          ▼
Load existing  Bootstrap new guest account
user record    and attach Instagram ID
    ╲          ╱
     ▼        ▼
Process message through conversational engine


Send reply back via Meta Send API

If the backend did not recognize the incoming scoped ID, it did not stop the conversation to demand an email address.

Instead, it automatically created a guest account record in the database, bound the Instagram identifier to that guest, initialized the user's state, and routed the incoming text into the conversational onboarding flow.

The user simply saw a natural reply in their DM inbox.

The interesting design principle here is that identity can sometimes be inferred from the channel.

The user had already authenticated with Instagram. Meta had already verified their device and their session. By receiving the message over an authenticated channel, the backend had enough stable identity to maintain state without forcing the user through a conventional registration gate.

The least fun part was Meta App Review

Building the webhook handler and the guest provisioning logic took an afternoon.

The part that tested my patience was getting the integration approved through Meta's developer portal.

To receive direct messages from users outside of a small test sandbox, your Meta Developer App has to pass Meta App Review. You have to request specific permissions:

  • instagram_manage_messages to read and respond to direct messages;
  • pages_manage_metadata or associated business assets to link the Instagram account to a Facebook page;
  • Webhook subscriptions verified against a secure hub.challenge handshake.

You have to submit screencasts, write explanations of why your app needs to read incoming messages, and wait for review teams to verify that your privacy policy and data handling comply with platform terms.

It is tedious, bureaucratic work.

Once the permissions were granted, though, the plumbing worked reliably. A user sent a message, Meta fired the webhook, the server replied within a few hundred milliseconds, and the user saw the text bubble appear in their Instagram thread.

Why it felt great at the beginning

In the beginning, this felt like magic.

Think about how people normally use Instagram. They scroll through their feed or watch reels on their phone. If they see a post or a profile mentioning a product, the usual handoff is:

tap bio link → open external browser → wait for page load → tap register → fill form → leave Instagram

Most people drop off at step two.

With the DM flow, the handoff was:

tap Message button → type "hey" → you are inside the product

The user never left the application they were already using. The conversational onboarding could ask a couple of quick questions to personalize their profile, remember their preferences, and immediately start helping them.

For the first forty-eight hours of testing, I thought this was the ideal onboarding model for conversational AI.

Then the structural limitations of the interface started to catch up.

Losing control of the chat interface

When you build your own client application, you own every pixel of the interface.

You can design:

  • a "New Chat" button to start a fresh session;
  • a sidebar listing distinct conversation threads;
  • separate tabs for notes, settings, and memory logs;
  • visual indicators showing which model or persona is active;
  • clear boundaries around different tasks or projects.

Inside Instagram DMs, you surrender all of that.

You do not get a sidebar. You do not get multiple threads. You do not get a button to clear the screen.

You get exactly one continuous, endless thread between the user and @omnisocialapp.

That sounds like a minor UI detail. It is actually a fundamental architecture problem.

The single thread collision

In an ordinary AI chat application, users separate their thoughts by creating new threads.

You might have:

  • Thread 1: drafting an email to a client;
  • Thread 2: brainstorming recipe ideas for dinner;
  • Thread 3: debugging a strange TypeScript error.

The user decides when a topic begins and when it ends simply by tapping a button. That button creates an explicit boundary. The system knows that what you said in Thread 1 has nothing to do with the code snippet in Thread 3.

In an Instagram DM, all of those interactions collide into the same stream.

On Monday, the user asks for help planning a weekend workout routine.

On Wednesday, they ask a question about JavaScript promises.

On Friday, they say:

"Hey, can you follow up on what we talked about earlier?"

Earlier what?

The workout? The JavaScript promise? Something they mentioned two weeks ago?

Because there is no native way for the user to create a new session, the system has to guess what context the incoming message belongs to.

If the backend simply takes the last twenty messages and pastes them into the prompt window, the AI gets confused by old, unrelated topics. If the backend wipes the context after an hour of inactivity, the companion loses the feeling of continuity that made the relationship appealing in the first place.

The bad solution: command syntax

The obvious engineering fix is to invent commands.

You could tell the user:

  • type /new to start a new topic;
  • type /clear to wipe recent context;
  • type /remember [fact] to save a memory;
  • type /mode notes to switch functionality.

I tried testing a version of that. It was awful.

The moment you require someone to remember slash commands inside an Instagram DM, you have ruined the entire point of using a messaging app.

You have taken the graphical user interface you threw away and rebuilt it as an awkward command-line interface. Nobody wants to type terminal syntax into a casual chat thread on their phone.

The lesson was clear:

When you embed an application inside someone else's interface, you lose the visual mechanisms you normally rely on to manage state.

That state does not disappear. It just gets pushed into the backend.

The AI has to absorb the missing UI

This experiment fundamentally changed how I thought about memory and conversational context.

Before this, I had treated conversation history the way most simple LLM wrappers did: as a basic array of messages (messages[]) that you pass into the API until the context limit fills up.

That approach works when the user manages their own thread boundaries.

When you only have one continuous thread, though, context cannot be a dumb array of recent strings. It has to become an intelligent system.

The system needs to figure out on its own:

  1. Working memory: What is the active topic being discussed right now?
  2. Episodic memory: What happened in previous conversations that might be relevant to this new message?
  3. Core entity facts: What permanent facts about the user should persist across all topics (their name, their goals, their preferences)?
  4. Transient noise: What messages were just temporary chatter that should be ignored when the topic changes?

In other words:

Memory is not just a database of past text. Memory is the mechanism that replaces the missing "New Chat" button.

When the interface cannot tell you where a context boundary sits, the backend has to reconstruct that boundary dynamically.

Where this led

This experiment was what originally sent me down the rabbit hole of retrieval, embeddings, and structured memory systems.

I started asking:

  • How do you selectively pull only the relevant pieces of past conversations without polluting the current prompt?
  • How do you let an AI companion recognize that a new message represents a topic switch without requiring a slash command?
  • How do you turn weeks of raw chat text into a compact, searchable set of permanent facts and relational lore?

Those questions eventually shaped the deeper memory and second-brain architecture I started building for Omni.

That retrieval and vector memory evolution is a much larger technical topic, and it deserves its own dedicated write-up.

What matters about this Instagram experiment is that it provided the catalyst. It showed me where naive conversational wrappers break down when you strip away the protective scaffolding of a custom chat UI.

Why this is no longer in production

This Instagram DM flow is no longer running in production.

I shut it down after the experiment concluded.

Maintaining a third-party messaging integration against evolving platform policies, rate limits, and review requirements takes real overhead. As Omni evolved from a social commerce experiment toward a dedicated AI companion and second-brain app, owning the native mobile experience with 3D presence, voice, and local storage became much more important.

The experiment was not a failure. It did exactly what an early prototype is supposed to do:

It removed an assumption I had taken for granted, forced an uncomfortable design problem into the open, and pointed directly at the real engineering work that needed to be done.

What I took away from the experiment

If you are building conversational software or considering third-party messaging channels, these are the trade-offs worth remembering:

  1. Channel identity is powerful. Bootstrapping an account from a verified messaging event removes massive signup friction. If your product can deliver immediate value within a single message, meeting users in their existing inbox is a potent distribution tool.
  2. Third-party interfaces remove control. When you build on Instagram, WhatsApp, Telegram, or Slack, you inherit their information architecture. You cannot add buttons, sidebars, or custom controls where the platform does not allow them.
  3. Continuous threads require explicit memory systems. If you cannot give users a "New Chat" button, your backend must become smart enough to detect context shifts, retrieve relevant history selectively, and discard noise.

I started the experiment trying to eliminate a login screen.

I ended it realizing that once you give an AI a continuous, never-ending conversation, memory is no longer a luxury feature.

It is the entire interface.

Back to work.