Automating Instagram DMs with Playwright (My First Browser Automation Project)
Sharing how I built a Playwright + Express automation to send Instagram DMs for testing, with sessions and n8n integration.
August 22, 2025 (1mo ago)
3 min read
β‘ Recently, I started exploring browser automation as part of my learning journey. I was curious: "Can I automate something like Instagram DMs just for testing?"
So I gave it a try with Playwright + Express.js, and in this blog Iβll share how I built it, the problems I faced, and what I learned. (And yes β this is strictly for educational/testing purposes, not spam π«)
InfoBefore we dive in: - β Donβt use this for bulk DM or spamming. - β Safe for testing, demos, and learning automation. - π« Misuse may get your account flagged.
π¦ Setting up the Project
First I created a fresh Node.js project and added the dependencies:
{ "name": "browser-automation", "version": "1.0.0", "main": "index.js", "license": "ISC", "dependencies": { "express": "^5.1.0", "playwright": "^1.54.2" } }
Then ran:
pnpm install npx playwright install # takes 5β8 min initially
π§© Writing the Instagram DM Automation
Hereβs the heart of my experiment β a simple Express server with a Playwright script.
When I send a request with a username + message, it automatically opens Instagram and sends the DM.
import express from "express"; import { chromium } from "playwright"; const app = express(); app.use(express.json()); const userDataDir = "./instagram_session"; // keeps session // Function to send Instagram DM async function sendInstagramMessage(page, username, message) { try { console.log(`[INFO] Sending message to @${username}...`); await page.goto(`https://www.instagram.com/${username}/`); await page.waitForSelector("text=Message", { timeout: 10000 }); await page.click("text=Message"); const box = page.locator( 'div[aria-label="Message"][contenteditable="true"]', ); await box.click(); for (const char of message) { await page.keyboard.type(char, { delay: 80 + Math.random() * 120 }); } await page.keyboard.press("Enter"); console.log(`[SUCCESS] Message sent to @${username}`); return { success: true }; } catch (err) { console.error(`[ERROR]`, err.message); return { success: false, error: err.message }; } }
π Testing the API
I exposed the function through an Express route, so I could test it like this:
curl -X POST http://localhost:3001/instagram \ -H "Content-Type: application/json" \ -d '{"username": "target_username", "message": "Hello from Playwright π"}'
And boom π it worked! Seeing the DM get typed in real-time was very satisfying.
βοΈ Sessions & Templates
One thing I learned early: persistent sessions are super useful.
By saving the login in ./instagram_session
, I didnβt need to log in every time.
I also added support for message templates to avoid hardcoding the text.
π Bonus: n8n Workflow
Since I also explore automation with n8n, I made a workflow that connects to this server. π Check the n8n Template JSON
Now I can trigger Instagram messages via n8n nodes β kind of like connecting Lego blocks.
β Key Takeaways
- Built my first Playwright + Express automation for Instagram DMs.
- Learned about persistent sessions and API-driven automation.
- Integrated it with n8n for more flexibility.
- And most importantly: this project is a reminder that automation is powerful β but needs to be used responsibly.
π₯ This was my first browser automation project with Playwright β and honestly, it made me more confident about exploring automation further.
Here are some other articles you might find interesting.
Building a Barcode Scanner & Generator in Next.js
How I researched, tested libraries, and built a simple yet powerful barcode scanner and generator app using Next.js.
Learning Only MERN Stack Isnβt Enough to Get an Internship in 2025
In 2025, MERN alone wonβt cut it. AI makes CRUD apps; you need full end-to-end skills to stand out