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:
- Behaviour that can already be represented as workflow graphs
- Behaviour that needs a reusable Workflows building block
- Behaviour that belongs elsewhere in Discourse rather than in Workflows
Current Salesforce plugin scope
The plugin provides the following features:
| Area | Current behaviour |
|---|---|
| API authentication | Salesforce JWT bearer flow, Redis-cached access token, and discovered Salesforce instance URL |
| Social login | Salesforce OAuth login through a managed Discourse authenticator |
| User linking | On user creation, search Salesforce Contacts and then Leads by email; store the resulting ID in a user custom field |
| Person creation | Staff actions to create a Salesforce Contact or Lead for a Discourse user |
| Group membership | Add linked users to salesforce-contacts or salesforce-leads groups |
| Feed publishing | Publish qualifying Discourse posts as Salesforce FeedItem records |
| Cases | Create Salesforce Cases from topics and publish replies as CaseComment records |
| Case status | Fetch Case status, store it locally, and update topic tags |
| Reconciliation | Every 12 hours, detect converted Leads and merged Contacts |
| Staff UI | Lead/Contact icons and links, Case status display, and manual synchronization actions |
| Diagnostics | Problem 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:
- Event triggers for topic creation, post creation, post edits, topic tag changes, category changes, and other Discourse events
- Scheduled and cron triggers
- Manual topic-admin and post buttons
- HTTP requests with dynamic URLs, methods, headers, query parameters, and JSON, form, or raw bodies
- Static Basic, Bearer, and header credentials
- Conditions, filtering, JavaScript transformations, item splitting, merging, and loops
- Topic actions, topic custom-field updates, tag changes, group membership changes, and user actions
- Data tables with insert, query, update, delete, upsert, and existence checks
- Error workflows, execution history, logging, waits, and webhook triggers
These are sufficient to describe much of the Salesforce plugin as workflow graphs.
| Salesforce behaviour | Workflows support today | Remaining qualification |
|---|---|---|
| Tagged topic creates a Case | Mostly available | Requires production Salesforce authentication and durable mapping |
| Edited topic refreshes or creates a Case | Mostly available | A post_edited trigger can start the same Case workflow |
| Reply creates a CaseComment | Mostly available | Requires an idempotent post-to-remote-record mapping |
| Non-Case post creates a FeedItem | Mostly available | Requires person lookup and rate-limit state |
| Case status updates topic tags | Available | HTTP request followed by topic tag actions |
| Scheduled Lead/Contact reconciliation | Mostly available | Schedule, HTTP, loops, and data tables are present |
| Linked user joins a group | Available | The group action supports add, remove, and membership checks |
| Manual Case synchronization | Partially available | Topic-admin buttons can trigger a workflow, but have limited dynamic presentation |
| Manual Contact or Lead creation | Partially available | Post buttons can trigger a workflow, but user email and custom-field writes are missing |
| Mapping Salesforce IDs | Partially available | Data tables work as a prototype mapping store but lack atomic uniqueness semantics |
| Salesforce webhook to Discourse | Available | This 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:
- JWT assertion generation and RS256 signing
- OAuth2 token acquisition
- Access-token expiry tracking
- Automatic token refresh
- Locking around concurrent refreshes
- Storage of provider metadata such as Salesforce’s returned
instance_url - Automatic injection of refreshed credentials into HTTP requests
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:
- Authorization code
- Client credentials
- Refresh token
- JWT bearer assertion
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
trigger:user_created- Permission-aware email access from the user action
custom_field_namesfor explicit user custom-field readsset_custom_fieldsfor explicit user custom-field writes- The same custom-field registration and access controls used by other Discourse APIs
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:
- Contact or Lead ID on the Discourse user
- FeedItem or CaseComment ID on the Discourse post
- Case ID, number, status, and last synchronization time in a local Case row
- A topic custom field indicating that a Salesforce Case exists
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:
- Configurable unique keys on data tables
- Atomic insert-if-absent or claim operations
- An idempotency-key expression for external mutations
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
- Configurable retry count
- Exponential backoff and jitter
- Retryable HTTP status selection
Retry-Aftersupport- Network-error retries
- Per-credential concurrency limits
- Keyed temporal rate limiting
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:
- Lead or Contact icon beside a poster
- Direct link to the Salesforce person record
- Case number and status card on the topic
- Case indicator in topic lists
- Dynamic Create Case versus Sync Case labels
- Immediate refresh after a user is linked
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:
- User or post badge
- Topic status icon
- Topic or post information card
- Dynamic label, icon, URL, and visibility expressions
- Staff or group visibility controls
- Refresh after workflow completion
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:
discourse-openid-connectdiscourse-oauth2-basic
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.
1. Link new users
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:
- Add
user_created, permission-aware user email access, and user custom-field reads/writes - Add a generic OAuth2/JWT credential framework with encrypted secret storage
- Add unique data-table keys and atomic idempotency claims
- Expose HTTP retry/backoff controls and add keyed temporal rate limiting
- Build and test the four Salesforce workflow templates
- Validate Salesforce social login through the bundled OpenID Connect or OAuth2 plugin
- Add generic workflow-backed status and record-link presentation where persistent UI parity is required
- 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:
- OAuth2/JWT credential lifecycle
- User creation, email, and custom-field support
- Atomic external-record mapping and idempotency
- Configurable HTTP resilience and temporal rate limiting
- 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.