outreach

outreach-send-core

Reusable outreach toolkit for backend projects that need:

It does not include app-specific preview URLs, unsubscribe token generation, branded templates, database schemas, or Local News article creation.

What It Handles

Included Adapters

Install

npm install outreach-send-core

Demo

The package is functional and ships with a runnable demo that exercises:

Live demo:

Run the mock preview locally with:

npm install
npm run demo

That mode uses mocked HTTP responses, so you do not need Brevo, Bouncer, Google Places, or live public-search credentials just to preview the package.

If you want the finder demo to behave like the real app and surface live diagnostics, run:

$env:PUBLIC_SERVICE_USER_AGENT='outreach-demo/1.0 (you@example.com)'
$env:GOOGLE_PLACES_API_KEY='your_key_here'
npm run demo:live -- --cities Torino --business-types restaurant --keywords pizza

For quick local startup, you can also use the launcher scripts:

npm run start:demo:live
npm run start:playground

On Windows, the direct wrappers work too:

scripts\demo-live.cmd
scripts\playground.cmd

The live demo keeps the send-engine example mock-backed, but runs GooglePlacesSeedProvider against live Google Places, OpenStreetMap, and public web results using the legacy multi-source acceptance logic. It prints:

You can tune the live finder with CLI flags or env vars such as:

If you want a browser UI for testing the package locally, run:

$env:GOOGLE_PLACES_API_KEY='your_key_here'
npm run playground

Then open http://127.0.0.1:4317.

The finder playground uses live provider calls for Google Places, OpenStreetMap, and public web search. It now exposes the live tuning knobs too, including:

If you leave Google Places enabled without a key, the provider will skip Google discovery and note that in diagnostics.

If you want the live site to publish from this repo, enable GitHub Pages for the main branch and /docs folder in the repository settings.

Example output:

Mock-backed demo for outreach-send-core.
No Brevo, Bouncer, Google Places, or live public-search credentials are required for this preview.

Outreach Engine Demo
====================
{
  "result": {
    "approvedCount": 3,
    "attemptedCount": 2,
    "sentCount": 1,
    "suppressedCount": 1,
    "skippedMissingPreviewCount": 1
  }
}

Prospect Finder Demo
====================
{
  "diagnostics": {
    "provider": "google_places",
    "strategy": "multi_source_province_acceptance"
  },
  "firstSeed": {
    "companyName": "Cafe Uno",
    "businessType": "restaurant",
    "primaryEmail": "booking.cafeuno@gmail.com"
  }
}

Production Quick Start

import {
  BrevoOutboundMailer,
  BouncerEmailVerifier,
  createOutreachEngine,
  evaluateBouncerEmailVerification,
} from 'outreach-send-core'

const outreach = createOutreachEngine({
  repository: myRepository,
  mailer: new BrevoOutboundMailer({
    apiKey: process.env.BREVO_API_KEY,
    fromEmail: process.env.EMAIL_FROM,
    fromName: process.env.EMAIL_FROM_NAME,
  }),
  emailVerifier: new BouncerEmailVerifier({
    apiKey: process.env.BOUNCER_API_KEY,
  }),
  evaluateEmailVerification: evaluateBouncerEmailVerification,
  resolveGlobalDailyCap: async () => 50,
  resolveReplyTo: async () => 'hello@example.com',
  linkBuilder: async ({ campaign, contact, preview }) => ({
    previewUrl: preview.publicUrl,
    unsubscribeUrl: `https://example.com/unsubscribe?email=${encodeURIComponent(contact.email)}`,
    registerUrl: `https://example.com/register?campaign=${campaign.id}`,
  }),
  templateBuilder: async ({ prospect, links }) => ({
    subject: `Preview for ${prospect.companyName}`,
    htmlContent: `<a href="${links.previewUrl}">Open preview</a>`,
    textContent: `Open preview: ${links.previewUrl}`,
    tags: ['outreach'],
  }),
})

await outreach.sendApprovedBatch('campaign_123')

Prospect Search Example

import { GoogleFirstSeedProvider } from 'outreach-send-core'

const provider = new GoogleFirstSeedProvider({
  apiKey: process.env.GOOGLE_PLACES_API_KEY,
  maxExpandedMunicipalities: 12,
  businessTypeLabels: {
    restaurant: 'restaurant',
    salon: 'hair salon',
  },
  nearbyTypesByBusinessType: {
    restaurant: ['restaurant'],
    salon: ['beauty_salon', 'hair_care'],
  },
})

const result = await provider.search({
  country: 'IT',
  language: 'it',
  cities: ['Torino'],
  provinces: [],
  businessTypes: ['restaurant'],
  keywords: ['pizza'],
  limit: 20,
})

The production provider now uses Google Places as the business identity source of truth, applies staged identity/type/website/contact gates, returns accepted prospects in seeds, and holds borderline prospects in reviewCandidates.

Each returned seed can now include:

Diagnostics now also expose:

Required Repository Contract

Your repository must implement:

See src/types.ts for the full interface definitions.

AI Agent Implementation Guide

If you are wiring this package with an AI coding agent, start with AGENTS.md.

That file gives the exact integration path:

Publish

npm run build
npm test
npm pack
npm publish

If you are publishing a public scoped package on npm, use:

npm publish --access public