{"id":259,"date":"2026-08-14T12:37:25","date_gmt":"2026-08-14T12:37:25","guid":{"rendered":"https:\/\/seannocode.com\/blog\/?p=259"},"modified":"2026-08-14T12:37:26","modified_gmt":"2026-08-14T12:37:26","slug":"the-free-manychat-alternative","status":"publish","type":"post","link":"https:\/\/seannocode.com\/blog\/the-free-manychat-alternative\/","title":{"rendered":"The free ManyChat alternative"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">ManyChat charges you a monthly subscription for one core trick: someone comments &#8220;LINK&#8221; on your reel, and a DM with your link lands in their inbox a second later.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That trick is a webhook, a keyword match, and one API call. It doesn&#8217;t need to cost anything.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/github.com\/diwenne\/openreply\">OpenReply<\/a> is an open-source (MIT) version of exactly that \u2014 Instagram comment-to-DM automation you self-host with your own API keys. No seat limits, no plan caps, no monthly bill. It uses Meta&#8217;s official Instagram API: no scraping, no browser automation, no handing over your password. Your account stays inside Meta&#8217;s rules, which matters if you&#8217;d like to keep it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In this post I&#8217;ll walk you through the full setup: hosting, environment, the Meta app (the part that actually takes time), and testing it end to end.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What you get<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Keyword \u2192 DM.<\/strong> One or many keywords per post, whole-word or partial match, with an optional public comment reply on top.<\/li>\n\n\n\n<li><strong>Tracked links.<\/strong> Your link becomes a tracked redirect with clicks and CTR per campaign \u2014 up to two tappable buttons per DM, each tracked separately.<\/li>\n\n\n\n<li><strong>Follow gate.<\/strong> Optionally require a follow before the link is released, verified against Meta&#8217;s actual follow status. It fails open, so a real follower never gets stuck.<\/li>\n\n\n\n<li><strong>Rate limiting built in.<\/strong> Stays under Meta&#8217;s 750-private-replies-per-hour cap and queues the overflow instead of dropping it.<\/li>\n\n\n\n<li><strong>Multi-account, workspaces, roles.<\/strong> Owner\/admin\/member with invite links \u2014 useful if you run this for clients.<\/li>\n\n\n\n<li><strong>Inbox and logs.<\/strong> Read and reply to DMs from the dashboard (inside Meta&#8217;s 24-hour window), and every send, skip, and failure is logged with a reason.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">How it&#8217;s built<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Two processes, two datastores. A Next.js web app serves the dashboard, the OAuth callback, and the incoming webhook. A separate long-running Node worker consumes the send queue and actually fires the DMs. They share a Postgres database (campaigns, logs, accounts) and Redis (the BullMQ queue and rate limiter).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The split matters for hosting: the web app runs happily on Vercel, but the worker can&#8217;t \u2014 serverless functions die, and a queue consumer has to stay up. So the recommended free-tier stack is Vercel for the web app and Railway for the worker plus Postgres and Redis.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">One rule you cannot break: the web app and worker must share the same <code>DATABASE_URL<\/code>, <code>REDIS_URL<\/code>, and <code>ENCRYPTION_KEY<\/code>. The web app encrypts your Instagram token; the worker decrypts it to send. Different keys and every single send fails.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What you need before you start<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>A Facebook account \u2014 Meta developer registration is built on it, there&#8217;s no Instagram-only path.<\/li>\n\n\n\n<li>An Instagram <strong>Business or Creator<\/strong> account. Personal accounts can&#8217;t connect \u2014 switch in the Instagram app under Settings \u2192 Account type.<\/li>\n\n\n\n<li>A free <a href=\"https:\/\/resend.com\">Resend<\/a> account with a verified sender domain. Login is email magic links only \u2014 no Resend, no login.<\/li>\n\n\n\n<li>Free Vercel and Railway accounts. You don&#8217;t need to buy a domain \u2014 the free <code>your-app.vercel.app<\/code> URL is what everything points at.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Set expectations now: the code deploys in minutes. The Meta app setup is where you&#8217;ll spend an afternoon the first time. That&#8217;s not OpenReply&#8217;s fault \u2014 it&#8217;s just Meta.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Option 1: Run it locally first<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">If you just want to poke around before deploying:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>git clone https:\/\/github.com\/diwenne\/openreply.git\ncd openreply\nnpm install\ncp .env.example .env      # fill in the values per docs\/setup.md\ndocker-compose up -d      # starts Postgres and Redis\nnpm run db:migrate\nnpm run dev               # web app on http:\/\/localhost:3000\nnpm run worker            # second terminal \u2014 this sends the DMs<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Two processes, always. <code>npm run dev<\/code> receives the webhooks; <code>npm run worker<\/code> sends the messages. If comments come in and no DM ever goes out, the worker is the first thing to check. For Meta to reach your local machine you&#8217;ll need a tunnel like <code>ngrok http 3000<\/code>, with your tunnel URL used everywhere a public URL is required.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Option 2: The production setup<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Step 1 \u2014 Railway: Postgres, Redis, worker<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Do Railway first, because Vercel needs the database URLs from it. Create a project, add PostgreSQL, add Redis, then add the worker as a GitHub Repo service pointing at your fork. Override the worker&#8217;s build and start commands:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Build Command:  npm run db:generate\nStart Command:  npm run worker<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Don&#8217;t leave the default <code>npm run build<\/code> \u2014 the worker doesn&#8217;t need a Next.js build, and any build step that touches the database will fail because the worker can&#8217;t reach Postgres at build time.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">On connection strings: Railway gives you two URLs per datastore. The internal ones (<code>*.railway.internal<\/code>) are for the worker \u2014 faster and free of egress inside Railway&#8217;s network. The public ones (<code>*.proxy.rlwy.net<\/code>) are for Vercel and for running migrations from your machine. Give Vercel an internal URL and it will hang and time out \u2014 this is one of the classic mistakes.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Step 2 \u2014 Migrate the production database<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>DATABASE_URL=\"postgresql:\/\/...proxy.rlwy.net...\/railway\" npm run db:migrate<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Run once from your machine using the public Postgres URL.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Step 3 \u2014 Vercel: the web app<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Import your fork as a new Vercel project (it auto-detects Next.js), add every environment variable, and deploy. Key values: <code>NEXTAUTH_URL<\/code> is your Vercel domain, <code>DATABASE_URL<\/code> and <code>REDIS_URL<\/code> are the <em>public<\/em> Railway URLs, and <code>ENCRYPTION_KEY<\/code> is the exact same value as on the worker.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Generate the secrets like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>openssl rand -base64 32   # NEXTAUTH_SECRET\nopenssl rand -hex 32      # ENCRYPTION_KEY (must be exactly 64 hex chars)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Plus a <code>CRON_SECRET<\/code> and a <code>WEBHOOK_VERIFY_TOKEN<\/code> (any random string \u2014 you&#8217;ll paste the same value into Meta&#8217;s webhook config later). The full variable table is in the repo&#8217;s <a href=\"https:\/\/github.com\/diwenne\/openreply\/blob\/main\/docs\/setup.md\">docs\/setup.md<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The Meta app: where the afternoon goes<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This is the slow part, and the setup guide is refreshingly honest about it. The short version, with the traps that catch everyone:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Create a Business-type app<\/strong> at developers.facebook.com and pick the &#8220;Manage messaging and content on Instagram&#8221; use case. <strong>Trap:<\/strong> don&#8217;t pick Facebook Login or the Marketing API use case \u2014 the first breaks the OAuth flow with a mismatched-client error, the second drags in heavy review requirements.<\/li>\n\n\n\n<li><strong>Collect three secrets.<\/strong> <code>INSTAGRAM_APP_ID<\/code> and <code>INSTAGRAM_APP_SECRET<\/code> from the Instagram product page, <code>FACEBOOK_APP_SECRET<\/code> from Settings \u2192 Basic. <strong>Trap:<\/strong> the Instagram app ID is a different number from the Facebook App ID. Use the one under the Instagram product.<\/li>\n\n\n\n<li><strong>Add your Instagram account as a tester \u2014 then accept the invite inside Instagram.<\/strong> This is the step everyone misses, and it produces the &#8220;Insufficient Developer Role&#8221; error. Sending the invite from the Meta dashboard is only half; you must open Instagram \u2192 Settings and activity \u2192 Apps and websites \u2192 Tester invites and accept. Until you do, login keeps failing.<\/li>\n\n\n\n<li><strong>Register the OAuth redirect<\/strong> in Business login settings: <code>https:\/\/your-app.vercel.app\/api\/instagram\/callback<\/code> \u2014 exact, no trailing slash.<\/li>\n\n\n\n<li><strong>Configure the webhook.<\/strong> Callback URL <code>https:\/\/your-app.vercel.app\/api\/webhook<\/code>, verify token = your <code>WEBHOOK_VERIFY_TOKEN<\/code>, subscribe to the <code>comments<\/code> field. Use the console&#8217;s Test \u2192 Send to My Server button to confirm delivery \u2014 it&#8217;s a two-step control, and only the second click actually POSTs.<\/li>\n\n\n\n<li><strong>Publish the app.<\/strong> Real comment webhooks only arrive when the app is Live. In Development mode, nothing happens outside the console Test button \u2014 this is the number one cause of &#8220;I set everything up and nothing works.&#8221; OpenReply ships the privacy, terms, and data-deletion pages Meta demands before it lets you publish, on your own domain.<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">Test it end to end<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Connect your account in the app (Settings \u2192 Connect Instagram), create a campaign on one of your posts with a keyword like <code>TEST<\/code>, then comment that keyword <strong>from a different account<\/strong> \u2014 OpenReply deliberately ignores your own comments. Watch for the DM.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If nothing arrives, <code>\/api\/health<\/code> is your friend: it reports the database, Redis, queue, and worker heartbeat. If <code>worker.healthy<\/code> is false, no DM will ever send no matter how perfect your Meta config is. Beyond that, the Postgres tables tell the whole story \u2014 <code>WebhookEvent<\/code> for delivery, <code>DmLog<\/code> for send status, <code>OperationalEvent<\/code> for worker errors.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Or: let an AI assistant drive it<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This is my favourite detail in the repo. The setup guide includes a ready-made prompt for Claude Code or Cursor \u2014 you open a clone of the repo in your assistant, paste the prompt, and it walks the entire setup with you: generates the secrets, deploys both processes, then steps you through the Meta dashboard one screen at a time, asking for values only you can provide. It even includes rules telling the assistant to diagnose failures by querying the Postgres tables directly instead of guessing.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">One caution the docs make and I&#8217;ll repeat: you&#8217;ll be pasting real secrets into a chat. Only do that in tooling you trust, and rotate anything you pasted once you&#8217;re live.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is exactly the pattern I teach inside my community \u2014 self-hosting the tools everyone else rents, with AI doing the heavy lifting on setup. If you want to go deeper on builds like this, that&#8217;s what <a href=\"https:\/\/www.skool.com\/ai-business-mentors-3843\/about\">AI Business Mentors<\/a> is for.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">One honest limitation<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Everything above covers running OpenReply for your own accounts, or a handful you add as testers \u2014 no App Review needed. If you want strangers to connect their own Instagram to your hosted instance, Meta requires Advanced Access review: a screencast of the full flow, written permission justifications (drafts are included in the repo), and business verification with real legal documents. Meta scrutinises automated-DM apps and often rejects the first submission. Most self-hosters skip this entirely and just run their own instance for their own account \u2014 which is the whole point anyway.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The bottom line<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">An afternoon of Meta dashboard pain buys you a ManyChat replacement that runs free on Vercel and Railway&#8217;s free tiers, keeps your account inside Meta&#8217;s official API, and tracks clicks per campaign. If comment-to-DM is part of how you distribute content \u2014 and if you&#8217;re gating lead magnets behind comments, it should be \u2014 this is one subscription you no longer need.<\/p>\n\n\n\n<div class=\"wp-block-group has-background has-global-padding is-layout-constrained wp-container-core-group-is-layout-2a43cda9 wp-block-group-is-layout-constrained\" style=\"border-radius:8px;background-color:#14304D;padding-top:40px;padding-right:40px;padding-bottom:40px;padding-left:40px\">\n<p class=\"has-text-color wp-block-paragraph\" style=\"color:#C29B3B;font-family:Public Sans;font-size:13px;letter-spacing:2px;text-transform:uppercase\">Build with me<\/p>\n\n\n\n<h3 class=\"wp-block-heading has-text-color\" style=\"color:#F3EEE3;font-family:Source Serif 4\">Stop renting your stack<\/h3>\n\n\n\n<p class=\"has-text-color wp-block-paragraph\" style=\"color:#9DAEC0;font-family:Public Sans\">Inside SeanNoCode I show you how to self-host tools like this, wire them into a real client-getting system, and build a one-person AI business around them \u2014 live Q&amp;A, course library, and a community of people actually shipping.<\/p>\n\n\n\n<div class=\"wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex\">\n<div class=\"wp-block-button\"><a class=\"wp-block-button__link has-text-color has-background wp-element-button\" href=\"https:\/\/www.skool.com\/ai-business-mentors-3843\/about\" style=\"border-radius:6px;color:#0C2136;background-color:#C29B3B;font-weight:600\">Join the community<\/a><\/div>\n<\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>ManyChat charges you a monthly subscription for one core trick: someone comments &#8220;LINK&#8221; on your reel, and a DM with your link lands in their inbox a second later. That trick is a webhook, a keyword match, and one API call. It doesn&#8217;t need to cost anything. OpenReply is an open-source (MIT) version of exactly [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":262,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_wp_convertkit_post_meta":{"form":"-1","landing_page":"0","tag":"0","restrict_content":"0"},"footnotes":""},"categories":[1],"tags":[],"class_list":["post-259","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-general-ai"],"_links":{"self":[{"href":"https:\/\/seannocode.com\/blog\/wp-json\/wp\/v2\/posts\/259","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/seannocode.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/seannocode.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/seannocode.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/seannocode.com\/blog\/wp-json\/wp\/v2\/comments?post=259"}],"version-history":[{"count":2,"href":"https:\/\/seannocode.com\/blog\/wp-json\/wp\/v2\/posts\/259\/revisions"}],"predecessor-version":[{"id":261,"href":"https:\/\/seannocode.com\/blog\/wp-json\/wp\/v2\/posts\/259\/revisions\/261"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/seannocode.com\/blog\/wp-json\/wp\/v2\/media\/262"}],"wp:attachment":[{"href":"https:\/\/seannocode.com\/blog\/wp-json\/wp\/v2\/media?parent=259"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/seannocode.com\/blog\/wp-json\/wp\/v2\/categories?post=259"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/seannocode.com\/blog\/wp-json\/wp\/v2\/tags?post=259"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}