The current discourse-salesforce plugin combines several kinds of functionality: Salesforce API authentication, event-driven synchronization, local mapping state, scheduled reconciliation, staff-facing controls, and Salesforce social login.

Discourse Workflows already provides many of the orchestration primitives used by the plugin. This analysis compares the Salesforce plugin at commit bd2435b with Discourse Workflows at Discourse commit 6efc5ea.

The purpose is to distinguish three categories:

  1. Behaviour that can already be represented as workflow graphs
  2. Behaviour that needs a reusable Workflows building block
  3. Behaviour that belongs elsewhere in Discourse rather than in Workflows

Current Salesforce plugin scope

The plugin provides the following features:

AreaCurrent behaviour
API authenticationSalesforce JWT bearer flow, Redis-cached access token, and discovered Salesforce instance URL
Social loginSalesforce OAuth login through a managed Discourse authenticator
User linkingOn user creation, search Salesforce Contacts and then Leads by email; store the resulting ID in a user custom field
Person creationStaff actions to create a Salesforce Contact or Lead for a Discourse user
Group membershipAdd linked users to salesforce-contacts or salesforce-leads groups
Feed publishingPublish qualifying Discourse posts as Salesforce FeedItem records
CasesCreate Salesforce Cases from topics and publish replies as CaseComment records
Case statusFetch Case status, store it locally, and update topic tags
ReconciliationEvery 12 hours, detect converted Leads and merged Contacts
Staff UILead/Contact icons and links, Case status display, and manual synchronization actions
DiagnosticsProblem checks for invalid credentials and Salesforce connected-app approval

The integration is primarily Discourse-to-Salesforce. It does not currently consume Salesforce webhooks or platform events. Case status is refreshed when Discourse initiates a synchronization. This means that general bidirectional conflict resolution, platform-event subscriptions, and Salesforce Bulk API support are not required for feature parity.

Capabilities already present in Workflows

Workflows currently includes:

These are sufficient to describe much of the Salesforce plugin as workflow graphs.

Salesforce behaviourWorkflows support todayRemaining qualification
Tagged topic creates a CaseMostly availableRequires production Salesforce authentication and durable mapping
Edited topic refreshes or creates a CaseMostly availableA post_edited trigger can start the same Case workflow
Reply creates a CaseCommentMostly availableRequires an idempotent post-to-remote-record mapping
Non-Case post creates a FeedItemMostly availableRequires person lookup and rate-limit state
Case status updates topic tagsAvailableHTTP request followed by topic tag actions
Scheduled Lead/Contact reconciliationMostly availableSchedule, HTTP, loops, and data tables are present
Linked user joins a groupAvailableThe group action supports add, remove, and membership checks
Manual Case synchronizationPartially availableTopic-admin buttons can trigger a workflow, but have limited dynamic presentation
Manual Contact or Lead creationPartially availablePost buttons can trigger a workflow, but user email and custom-field writes are missing
Mapping Salesforce IDsPartially availableData tables work as a prototype mapping store but lack atomic uniqueness semantics
Salesforce webhook to DiscourseAvailableThis exceeds the current plugin’s feature set

Gap 1: OAuth2 and JWT credential lifecycle

The Salesforce API client uses the JWT bearer grant:

sign an assertion with an RSA private key
  → POST /services/oauth2/token
  → receive access_token and instance_url
  → cache the token
  → authenticate subsequent API requests

The Workflows HTTP node currently supports static Basic, Bearer, and header credentials. It does not provide:

A manually entered bearer token is enough to prototype a workflow, but not enough for an unattended integration.

Required building block

A generic OAuth2 credential framework should support at least:

A resolved credential should be able to provide both request authentication and connection metadata:

{
  "access_token": "...",
  "base_url": "https://example.my.salesforce.com",
  "expires_at": "2026-07-30T04:00:00Z"
}

The HTTP node could then resolve its Authorization header and optional base URL from the credential.

Workflow credentials are stored in a JSONB column and redacted when serialized to the administration client. There is no application-level encryption visible in the credential model. RSA private keys and refresh tokens should use encrypted secret storage before an OAuth credential framework is introduced.

Gap 2: User lifecycle, email, and custom fields

The Salesforce plugin starts user reconciliation from Discourse’s user_created event. Workflows does not currently expose a user_created trigger.

The workflow user schemas also omit email, while the Salesforce lookup is keyed by email. The existing user action can update profile text, title, trust level, and trust-level lock, but cannot read or write arbitrary user custom fields.

The required sequence is therefore not currently expressible:

user created
  → read verified email
  → query Salesforce Contact by email
  → query Salesforce Lead if no Contact exists
  → save the Salesforce ID on the user
  → add the user to the corresponding group

Required building blocks

These additions are useful beyond Salesforce and align the user action with the topic action, which already supports selected custom-field reads and writes.

Gap 3: Durable mappings and idempotency

The plugin records Salesforce IDs in several places:

These records are both mappings and guards against duplicate exports.

A Workflows replacement could use data tables:

salesforce_people
  discourse_user_id, salesforce_type, salesforce_id

salesforce_cases
  topic_id, salesforce_case_id, number, status, last_synced_at

salesforce_posts
  post_id, salesforce_object_type, salesforce_id

Data-table filtering and upsert make this possible, but they do not expose a database-enforced unique key or an atomic claim operation. Two concurrent executions can both observe that a mapping is absent, both create a Salesforce object, and then race while recording the result.

Required building blocks

At minimum:

A more general external-record mapping primitive could standardize:

provider, remote_type, remote_id
local_type, local_id
state, last_synced_at

This would be reusable for CRM, issue tracker, support desk, and mailing-list integrations.

Gap 4: HTTP retries and rate limiting

The internal workflow HTTP client contains retry-related code, but the HTTP node does not expose retry count or retry status configuration. The effective default is no retry. Failed workflow executions are recorded and may invoke an error workflow, but the execution does not propagate the failure back to Sidekiq for an automatic job retry.

The current Salesforce plugin also has limited retry handling, so this is not a strict feature-parity blocker. It is still relevant when moving an integration onto a general workflow platform.

Required building blocks

The existing limit node limits the number of items passed through a graph. It does not implement a quota such as “N FeedItems per Salesforce record per day,” which the current plugin supports.

Gap 5: Persistent UI presentation

Workflows can add topic-admin and post buttons and can display transient modals. This covers the basic manual actions.

It does not cover the persistent Salesforce presentation supplied by the plugin:

Possible reusable building block

If Workflows is intended to own this class of integration UI, it needs a declarative decoration system backed by workflow state, data tables, or custom fields:

This should remain generic. Salesforce-specific components inside the Workflows plugin would not reduce the amount of integration-specific code; they would only relocate it.

Salesforce social login

Salesforce login should not be implemented as a workflow. Authentication providers run before a normal user workflow context exists and have separate security and failure requirements.

Discourse already bundles:

Salesforce provides OAuth2 and OpenID Connect endpoints. The appropriate replacement path is to validate Salesforce login against the generic OpenID Connect plugin first, and the generic OAuth2 plugin if necessary.

The current Salesforce authenticator verifies Salesforce’s token-response signature. An OpenID Connect replacement would instead rely on the normal issuer, nonce, and signed ID-token validation path. This requires an end-to-end configuration test before the dedicated authenticator can be removed.

Proposed workflow decomposition

Once the reusable gaps are addressed, the Salesforce behaviour can be represented as four workflows.

user_created
  → query Salesforce Contact by email
  → if absent, query Salesforce Lead
  → write user custom field
  → add user to contacts or leads group
  → upsert external mapping

2. Create or refresh a Case

Triggered by a tagged topic, a post edit, or a topic-admin button:

load topic and author
  → claim topic mapping
  → optionally find or create Contact
  → create Case if missing
  → fetch current Case details
  → update mapping and topic custom field
  → apply Case and status tags

3. Export posts

post_created
  → claim post mapping
  → inspect topic Case mapping
  → create CaseComment if a Case exists
  → otherwise load person mapping and create FeedItem
  → record returned Salesforce ID

4. Reconcile people

schedule every 12 hours
  → load Salesforce person mappings
  → batch by 100
  → call Salesforce Composite API
  → update Lead-to-Contact conversions
  → update merged Contact IDs
  → update groups and custom fields

Implementation sequence

A direct implementation sequence is:

  1. Add user_created, permission-aware user email access, and user custom-field reads/writes
  2. Add a generic OAuth2/JWT credential framework with encrypted secret storage
  3. Add unique data-table keys and atomic idempotency claims
  4. Expose HTTP retry/backoff controls and add keyed temporal rate limiting
  5. Build and test the four Salesforce workflow templates
  6. Validate Salesforce social login through the bundled OpenID Connect or OAuth2 plugin
  7. Add generic workflow-backed status and record-link presentation where persistent UI parity is required
  8. Migrate existing Salesforce IDs and Case records, then remove the dedicated plugin

Summary

Workflows already provides the event handling, scheduling, HTTP execution, control flow, topic actions, group actions, data storage, and manual triggers needed for most of the Salesforce integration.

The principal missing platform capabilities are:

  1. OAuth2/JWT credential lifecycle
  2. User creation, email, and custom-field support
  3. Atomic external-record mapping and idempotency
  4. Configurable HTTP resilience and temporal rate limiting
  5. Persistent, data-backed UI decorations

Salesforce social login is a separate concern and should be handled by the existing generic authentication plugins. With those boundaries, the Salesforce-specific implementation becomes a small collection of workflow templates and configuration rather than a dedicated integration plugin.