Bulk Messaging System

An Electron Desktop app for marketing campaign & broadcast management, using either whatsapp, gmail or a SMTP server

Desktop application architecture

Introduction#

Electron main process, React renderer, preload bridge, and Python helpers. Where each concern lives and how they talk.

Project structure#

  • Electron main process and preload under electron/src/electron (main.js, preload.cjs, handlers)
  • React UI under electron/src/ui and components under electron/src/components
  • Shared contact parsers under electron/src/shared
  • Bundled frontend assets under electron/dist-react
  • Vite and electron-builder config at the electron/ root

package.json "main" is src/electron/main.js. The BrowserWindow preload path is preload.cjs.

Core components#

  • Main process: Creates the BrowserWindow, sets webPreferences, registers IPC, manages lifecycle, and owns Gmail, SMTP, and WhatsApp clients.
  • Preload bridge: Exposes window.electronAPI through contextBridge. Invoke for requests, on helpers for events.
  • Renderer (React 19): Functional components keep UI state, send work to main over IPC, and render progress.
  • Handlers: Gmail OAuth and send, SMTP verify and send.
  • Utilities: Pyodide can run parse_manual_numbers.py in the renderer; Flask in python-backend/ is the full parser.

Architecture overview#

Strict main/renderer split:

  • Main process runs filesystem, network, and provider clients.
  • Renderer draws UI and delegates through typed IPC.
  • Preload defines the only API the page may call.

Detailed component analysis#

Main process responsibilities#

  • Window creation with context isolation and nodeIntegration: false.
  • IPC for Gmail, SMTP, WhatsApp, templates, and file dialogs.
  • Lifecycle: startup cleanup, window-all-closed, before-quit.
  • Status events to the renderer.

Preload bridge and secure IPC#

preload.cjs exposes one electronAPI object. ipcRenderer.invoke for request/response, ipcRenderer.on for streams. Listener helpers return an unsubscribe function.

Gmail handler#

  • OAuth2 in a dedicated BrowserWindow.
  • Tokens in electron-store.
  • Gmail API send with progress events.

SMTP handler#

Validates config, verifies transport, sends with progress. Partial config can persist in electron-store.

WhatsApp integration#

LocalAuth plus Puppeteer. QR as a data URL. CSV/text import and mass send with delays.

React + electron integration#

main.jsx mounts App.jsx, which renders BulkMailer.jsx. BulkMailer owns tab state and wires forms to window.electronAPI.

Python backend utilities via pyodide#

Loads Pyodide and parse_manual_numbers.py when needed, then returns JSON contacts.

Dependency analysis#

  • Electron main depends on handlers, dialogs, whatsapp-web.js, googleapis, nodemailer, electron-store.
  • Preload depends on contextBridge and ipcRenderer.
  • Renderer depends on React 19, Tailwind 4, and the preload API.
  • Vite writes dist-react, which production loadFile uses.

Performance considerations#

  • Headless browser for WhatsApp with sandbox and GPU disabled.
  • Rate limiting delays between messages.
  • Event-driven progress so the UI stays live.
  • Cache and auth cleanup on logout and close.
  • Tailwind utilities instead of a heavy CSS framework.

Security model#

  • Context isolation on.
  • Node integration off. Remote module off.
  • Preload exposes only listed methods.
  • Typed IPC channels.
  • OAuth secrets in env. Tokens in electron-store.
  • webSecurity on.

Build system#

Vite builds the React UI into dist-react with base: "./". Scripts in electron/package.json run dev (npm run dev), production (npm run prod / npm run start), and packaging (dist:mac, dist:win, dist:linux). electron-builder 26 ships macOS dmg (arm64), Linux AppImage (x64), Windows portable and msi. Packaged files: dist-react/**, src/electron/**, src/shared/**, package.json.

Component interaction diagrams#

Gmail workflow#

WhatsApp workflow#

SMTP workflow#

Troubleshooting guide#

  • WhatsApp QR not loading: retry, check status events, install Chrome/Brave or set PUPPETEER_EXECUTABLE_PATH.
  • Gmail authentication timeout: .env in electron/, redirect URI matches the desktop client.
  • SMTP verification failure: host, port, TLS, then transporter.verify().
  • File import issues: CSV/text headers and UTF-8.
  • Dev vs production: dev loads http://localhost:5173; production loads dist-react/index.html.

Conclusion#

Main owns secrets and clients. Renderer owns UI state. Cross that line only through preload-defined IPC.