Constant Contact

Lightweight, dependency-free, in-memory Constant Contact v3 API fake for testing code that uses the language-agnostic Constant Contact v3 REST API.

Default port: 4832

Quick start

Start the server:

import { ConstantContactServer } from "./services/constant-contact/src/server.js";

const server = new ConstantContactServer(4832);
await server.start();
// ... run your app/tests ...
await server.stop();

Point a client at it (Bearer auth):

await fetch("http://127.0.0.1:4832/v3/contacts", {
  method: "POST",
  headers: { Authorization: "Bearer parlel-cc-token", "Content-Type": "application/json" },
  body: JSON.stringify({
    email_address: { address: "contact@parlel.dev", permission_to_send: "implicit" },
    first_name: "Contact",
    create_source: "Account",
  }),
});
// => { contact_id, email_address, first_name, ... }

Email campaign creation is captured and inspectable via /__parlel/*.

Access via MCP / preview URL

When run under the parlel pool, this service is reachable through the MCP gateway and a preview URL at http://127.0.0.1:4832. Use CONSTANT_CONTACT_BASE_URL to point clients/agents at it. Captured campaigns live at GET /__parlel/messages.

Implemented operations

All /v3/* routes require Bearer auth. State is in-memory and ephemeral.

Service & inspection operations (parlel extensions)

Surface coverage

This emulator faithfully replicates the API surface most application code and agents exercise. Anything below the supported lines is either an intentional design choice for a fast, zero-cost local emulator (✓ By design) or a candidate for a future release (⟳ Roadmap) — never a silent inaccuracy.

Legend: ✅ fully supported · ◐ accepted (stored, not strictly enforced) · ✓ by design · ⟳ on the roadmap.

FeatureStatus
Contacts CRUD (create/list/get/update/delete)✅ Supported
Contact lists (create/list)✅ Supported
Email campaign creation✅ Supported
Account summary✅ Supported
Captured campaign inspection✅ Supported (parlel extension)
Actual campaign delivery / SMTP✓ By design — Captured in-memory for inspection — no real messages sent
OAuth2 token exchange / refresh✓ By design — Any non-empty credential is accepted — no real secrets needed
Campaign scheduling / sending / activities lifecycle◐ Created as Draft; not advanced
Segments / tags / bulk activities / reporting⟳ Roadmap
Rate limiting (429)✓ By design — Never throttles — local tests run at full speed, zero cost

Error shapes

Errors use the Constant Contact envelope [{ "error_key": "...", "error_message": "..." }].

StatusWhen
401missing/invalid Bearer auth
400invalid/missing field (e.g. email_address.address, name)
409contact already exists
404unknown contact / endpoint

Manifest

See services/constant-contact/manifest.json:

<!-- parlel:testenv:start -->

Configuration — test.env

Copy these into your test.env (used by the bridge sidecar flow). Tokens are Parlel's seeded test credentials — any non-empty value is accepted by the emulator, so you rarely need to change them. Swap in real credentials only when pointing at the live service in prod.env.

CONSTANT_CONTACT_ACCESS_TOKEN=parlel-cc-token
CONSTANT_CONTACT_BASE_URL=http://parlel-bridge:4832
<!-- parlel:testenv:end -->