DocumentationB2B SaaS integration pattern

B2B SaaS integration pattern

Contacts sync, campaigns API, and bidirectional webhooks for embedded newsletter.

This page describes a reference architecture for a B2B SaaS product that embeds MailingCore newsletter capabilities: your app remains the source of truth for consent, while MailingCore holds an operational replica for sending, hosted unsubscribe, and deliverability.

The pattern was validated in production-style integrations (multi-tenant family apps, CRM add-ons, vertical SaaS). Adapt names and IDs to your domain.

Architecture overview

API key scopes (minimum)

ScopeUse
contacts:readReconciliation (updatedSince)
contacts:writeUpsert, bulk import, erasure
campaigns:readPoll status and stats
campaigns:writeCreate, test, schedule, send
webhooks:manageRegister your receiver URL

Optional: suppressions:write if you push unsubscribes from your admin panel in addition to webhooks.

1. Contact synchronization

Goal: Keep opted-in audience in MailingCore without passing recipient lists on every send.

AspectRecommendation
IdentityexternalId = your stable user/account ID (opaque, no extra PII)
UpsertPUT /contacts when consent is granted
Initial loadPOST /contacts/bulk in chunks (≤ 1000 per request)
IncrementalPeriodic job + on-change hooks when email, locale, or consent changes
Fieldsemail, locale, optIn: true, consentVersion, source tag
Revoke in your appDELETE /contacts/:externalId or PATCH with optIn: false
GDPR erasureDELETE /contacts/:externalId — MailingCore propagates suppression

Reconciliation: GET /contacts?updatedSince=<ISO>&cursor= to align hosted unsubscribes back to your consent store.

Suggested cadence: every 15–60 minutes plus real-time hooks on consent events.

2. Webhook receiver (your server)

Register with Create endpoint:

{
  "url": "https://your-saas.com/webhooks/mailingcore",
  "events": [
    "contact.unsubscribed",
    "email.bounced",
    "email.complained"
  ],
  "secret": "<your-env-secret>"
}
EventTypical action in your SaaS
contact.unsubscribedRevoke newsletter consent by externalId or email; audit log
email.bounced (hard)Mark email invalid; optional suppression sync
email.complainedRevoke consent; operator alert

Verify X-MailingCore-Signature (HMAC SHA-256, ±5 min replay tolerance). Respond 200 immediately; process async. See Deliveries and retries.

3. Campaign workflow (replaces batch relay)

Stop fan-out POST /emails/batch with explicit recipient arrays. Use the campaigns API instead:

  1. Pre-sync audience

    Run contact sync so optIn: true contacts are current.

  2. Create draft

    POST /campaigns with templateVersionId, subject, audienceFilter: { optIn: true }.

  3. Check locale coverage

    GET /campaigns/:id/locale-coverage — ensure template variants exist per Locale coverage.

  4. Test send

    POST /campaigns/:id/test to 1–5 internal QA addresses.

  5. Schedule or send

    POST /campaigns/:id/schedule or POST /campaigns/:id/send (async fan-out).

  6. Poll status

    GET /campaigns/:id until SENT, FAILED, or CANCELLED. Persist mailingcoreCampaignId in your campaign entity.

Full step-by-step: Create and send a campaign.

4. Feature flag and rollback

Keep a configuration switch (e.g. NEWSLETTER_MODE=legacy_relay | mailingcore_api) so you can revert to your previous send path without redeploying. Run smoke tests on staging before flipping production traffic.

5. Smoke test checklist

#ScenarioPass criteria
1Upsert test contacts with distinct localesListed in GET /contacts; optIn: true
2Campaign test sendEmail received; {{unsubscribeUrl}} present
3Multi-locale audiencelocale-coverage warnings coherent
4Small production sendstatus: SENT; stats sent > 0
5Click hosted unsubscribecontact.unsubscribed received; consent revoked in your app
6Revoke consent in your appContact removed or opted out in MailingCore
7Idempotent sync retryNo duplicate externalId rows

Related