Skip to content

Instantly share code, notes, and snippets.

@esafwan
Created May 28, 2025 04:53
Show Gist options
  • Select an option

  • Save esafwan/54f724062f637b87ca53f64ad652e298 to your computer and use it in GitHub Desktop.

Select an option

Save esafwan/54f724062f637b87ca53f64ad652e298 to your computer and use it in GitHub Desktop.

WhatsApp Bulk Sender Setup

  1. Initialize the Project:

    npm init -y
  2. Install Dependencies:

    npm install axios csv-parser dotenv
  3. Create Required Files:

    • bulk-send.js (this file)
    • .env with your WhatsApp API credentials and config:
      WHATSAPP_TOKEN=your_access_token_here
      PHONE_NUMBER_ID=your_phone_number_id
      INPUT_CSV=contacts.csv
      MIN_DELAY_SECONDS=1
      MAX_DELAY_SECONDS=3
    • contacts.csv in this format:
      phone,message
      971501234567,Hello Safwan!
      971508765432,Your order has been shipped.
      
  4. Run the Script:

    node bulk-send.js
  5. Expected Output:

    • logs/send-<min>-<hr>-<date>-<month>-<yy>-<sno>.log: log of successes/failures
    • failed/failed-<min>-<hr>-<date>-<month>-<yy>-<sno>.csv: list of failed messages with error reason

Note: Try to keep the CSV below 500 per job to reduce the chance of getting blocked.

const fs = require('fs');
const csv = require('csv-parser');
const axios = require('axios');
const { promisify } = require('util');
const sleep = promisify(setTimeout);
require('dotenv').config();
const token = process.env.WHATSAPP_TOKEN;
const phoneNumberId = process.env.PHONE_NUMBER_ID;
const inputCsv = process.env.INPUT_CSV || 'contacts.csv';
const minDelay = parseInt(process.env.MIN_DELAY_SECONDS || '1') * 1000;
const maxDelay = parseInt(process.env.MAX_DELAY_SECONDS || '3') * 1000;
const now = new Date();
const timestamp = `${now.getMinutes()}-${now.getHours()}-${now.getDate()}-${now.getMonth() + 1}-${now.getFullYear()}`;
let counter = 1;
const logsDir = 'logs';
const failedDir = 'failed';
if (!fs.existsSync(logsDir)) fs.mkdirSync(logsDir);
if (!fs.existsSync(failedDir)) fs.mkdirSync(failedDir);
const logFile = `${logsDir}/send-${timestamp}-${counter}.log`;
const failedCsv = `${failedDir}/failed-${timestamp}-${counter}.csv`;
const logStream = fs.createWriteStream(logFile, { flags: 'a' });
const failedStream = fs.createWriteStream(failedCsv);
failedStream.write('phone,message,error\n');
const sendMessage = async (to, message) => {
try {
const res = await axios.post(
`https://graph.facebook.com/v19.0/${phoneNumberId}/messages`,
{
messaging_product: 'whatsapp',
to,
type: 'text',
text: { body: message },
},
{
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
}
);
logStream.write(`[SUCCESS] ${to}: ${message}\n`);
console.log(`βœ… Sent to ${to}`);
} catch (err) {
const errorMsg = err.response?.data?.error?.message || err.message;
logStream.write(`[FAILURE] ${to}: ${errorMsg}\n`);
failedStream.write(`"${to}","${message}","${errorMsg}"\n`);
console.error(`❌ Failed for ${to}: ${errorMsg}`);
}
};
const processCsv = async () => {
const rows = [];
fs.createReadStream(inputCsv)
.pipe(csv())
.on('data', (row) => {
if (row.phone && row.message) rows.push(row);
})
.on('end', async () => {
console.log(`πŸ“‹ ${rows.length} messages to process...`);
for (const row of rows) {
await sendMessage(row.phone, row.message);
const delay = Math.floor(Math.random() * (maxDelay - minDelay + 1)) + minDelay;
await sleep(delay);
counter++;
}
logStream.end();
failedStream.end();
console.log('βœ… Processing completed.');
});
};
processCsv();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment