Skip to main content

Salesforce User Provisioning: JIT, SCIM and Deprovisioning

Updated Add a comment
💬 In plain words: User provisioning spans a spectrum from creating records by hand, to pushing user updates ahead of time via a System for Cross-domain Identity Management (SCIM) or REST API integration, to Just-In-Time (JIT) provisioning. With JIT, nobody creates Salesforce users in advance: when a user logs in via Single Sign-On (SSO) for the first time, Salesforce reads the incoming SAML 2.0 assertion and creates or updates the User record on the spot. However, because JIT only runs during login, it never deactivates users when they leave—requiring a dedicated offboarding process.
📌 Illustrative Partner Onboarding Scenario: Suppose your company needs to onboard 5,000 external dealer portal users who already authenticate against their corporate Identity Provider (IdP). Instead of importing 5,000 CSV rows before launch, you enable SAML 2.0 JIT provisioning. When a dealer logs in for the first time, the SAML assertion passes their name, email, and dealer code. Your custom JIT handler matches the dealer code to the parent Account, creates their Contact and portal User, and assigns the appropriate profile. Their first login completes the entire onboarding workflow.

🔑 Key Points

  • JIT Eliminates Pre-Provisioning: Just-In-Time provisioning uses attributes embedded in a SAML 2.0 assertion (or an OpenID Connect Registration Handler) to create new users and update existing users dynamically during login.
  • Standard vs. Custom SAML JIT: Standard JIT declaratively maps SAML attribute names (such as User.Email and User.ProfileId) without code. Custom JIT invokes an Apex class implementing Auth.SamlJitHandler so you can look up profile names, resolve parent accounts, and handle complex logic.
  • The Deprovisioning Blind Spot: JIT only executes when a user actively logs in. Disabling a terminated employee or contractor in your IdP blocks interactive SSO logins, but leaves their Salesforce User active, consumes a paid license, and leaves previously authorized OAuth refresh_token grants alive.
  • SCIM Handles the Full Lifecycle: Push-based provisioning from an IdP (such as Okta or Microsoft Entra ID using SCIM / Salesforce REST APIs) manages Joiners, Movers, and Leavers—immediately freezing or deactivating users when they leave the organization.
  • Immutable Matching Keys: Always match JIT and SCIM users on an immutable FederationIdentifier (Federation ID) rather than email address, and store OAuth refresh tokens strictly in secure server-side vaults rather than browser storage.
🧠 Core Rule: Just-In-Time (JIT) provisioning handles Joiners and Movers at the moment of login, while SCIM or automated API sync handles the entire user lifecycle—including deactivating Leavers and revoking active tokens.

🔍 Concept: The Salesforce User Provisioning Spectrum

Before a person can view a record or run a report in Salesforce, a corresponding User record (and for external Experience Cloud users, an associated Account and Contact record) must exist in the database. Depending on your user volume, license type, and identity architecture, you can choose from four primary provisioning models:

Provisioning Spectrum
├─ 1. Manual Creation ────────── Admin creates each User record in Setup before login
├─ 2. Self-Registration ──────── External user signs up via Experience Cloud handler
├─ 3. Just-In-Time (JIT) ─────── SAML 2.0 assertion creates/updates User at login
│       ├─ Standard JIT (Declarative SAML attribute mapping)
│       └─ Custom JIT   (Apex class implementing Auth.SamlJitHandler)
└─ 4. SCIM / Directory Sync ──── IdP pushes Joiner, Mover, AND Leaver updates via API

1. Manual Administration and Bulk CSV Loads

Creating users manually in Setup or importing them with Data Loader works well for a small, stable internal team or for provisioning emergency break-glass System Administrators. Beyond a few dozen users, however, manual creation slows down onboarding and drifts out of sync with HR and corporate directories.

2. Experience Cloud Self-Registration

For B2C consumer portals where external customers do not belong to a corporate Identity Provider, users fill out a public sign-up form or click a social login button ("Login with Google/Apple"). Behind the scenes, an Experience Cloud self-registration controller or an OpenID Connect Auth.RegistrationHandler creates their PersonAccount (or Contact) and external User record on demand.

3. Just-In-Time (JIT) Provisioning over SAML 2.0

When your workforce or B2B partner population authenticates through a SAML 2.0 Identity Provider, Just-In-Time (JIT) provisioning lets you skip advance user creation entirely. You check User Provisioning Enabled inside your Salesforce Single Sign-On Settings and set the SAML Identity Type to match on Federation ID (FederationIdentifier).

When a user logs in at the IdP, the IdP sends a digitally signed SAML 2.0 XML assertion to Salesforce containing the user's FederationIdentifier alongside attribute statements for their name, email, and role. Salesforce checks whether a User with that FederationIdentifier already exists:

  • If no matching user exists: Salesforce inserts a new User record (and for Customer/Partner portal users, creates or links the Account and Contact) using the values in the SAML assertion, and then logs the user in immediately.
  • If a matching user exists: Salesforce updates the existing User record with any changed attributes from the assertion before completing the login.

Salesforce offers two ways to process SAML JIT assertions:

  • Standard SAML JIT: Requires zero code. The IdP formats SAML attribute names using exact Salesforce schema prefixes—such as User.Username, User.Email, User.LastName, and User.ProfileId (see SAML JIT Requirements in Salesforce Help). Standard JIT is fast to enable, but it requires the IdP to send exact Salesforce Profile IDs or names and cannot execute custom SOQL queries or assign Permission Sets cleanly without specific workarounds.
  • Custom SAML JIT (Auth.SamlJitHandler): You select Custom SAML JIT with Apex handler in Single Sign-On Settings and designate an Apex class implementing the Auth.SamlJitHandler interface, along with an administrative Execute Handler As user. Custom JIT gives you full Apex control to translate business attributes (such as Department=Sales) into Salesforce Profile IDs, handle missing optional fields gracefully, and log detailed errors.

4. SCIM and Push-Based Directory Synchronization

While JIT is convenient, it is strictly pull-on-login. By contrast, enterprise Identity Providers like Okta, Microsoft Entra ID, and PingOne support outbound provisioning connectors built on the System for Cross-domain Identity Management (SCIM) specification and Salesforce's standard REST APIs. Note that while older study guides sometimes mention Salesforce Identity Connect for Active Directory sync, Salesforce retired Identity Connect; modern architectures rely directly on your IdP's native provisioning connector or an HR/iPaaS integration (such as Workday-to-MuleSoft-to-Salesforce).

With push-based SCIM provisioning, the moment HR hires an employee, changes their department, or terminates their contract, the IdP makes an immediate background API call to Salesforce to create, update, freeze, or deactivate the User record—without waiting for the user to log in.

Architectural comparison diagram of the Salesforce user provisioning spectrum, showing Manual Creation, Experience Cloud Self-Registration, SAML Just-In-Time (JIT) Provisioning, and SCIM Directory Sync

🎬 Scenario Walkthrough: The Terminated Contractor Who Still Held a Live Refresh Token

Consider a realistic enterprise security incident: an external data contractor finished their assignment in March. Six months later, in September, a security review of Salesforce API logs discovered that a script running on an external server was still authenticating as that contractor every night at 2:00 AM and exporting Opportunity data.

The Flawed Approach: The organization relied exclusively on SAML Just-In-Time (JIT) provisioning for user lifecycle management. When the contractor left in March, IT disabled their account inside the corporate Identity Provider and assumed access to all downstream cloud apps was terminated.

Why This Breaks: Disabling a user in the IdP only stops them from completing a new browser-based SSO handshake. Because JIT only runs during a successful SAML login, Salesforce never received any signal that the contractor left. Their Salesforce User record remained IsActive = true, continued consuming a paid Salesforce license, and—most dangerously—an OAuth 2.0 refresh_token that the contractor had previously authorized for a Connected App continued minting fresh access_token sessions every night without ever touching the IdP.

The Resilient Approach: Pair SSO with automated outbound deprovisioning from the IdP (via SCIM or an HR termination webhook). The moment a user is terminated upstream, the integration freezes or deactivates the Salesforce User record, revokes all active AuthSession and OauthToken grants, reclaims the license, and queues a batch job to transfer owned records to their manager.

The Payoff: Both interactive browser access and headless API tokens terminate on the exact day the contractor leaves. Better yet, testing this workflow with a simulated "test leaver" account before go-live provides concrete audit evidence for compliance reviews.

🛠️ Worked Example: Custom SAML JIT Handler and Mixed-DML Considerations

When you write a Custom SAML JIT handler in Apex, you implement two methods from the Auth.SamlJitHandler interface: createUser (which constructs and returns an uninserted User sObject for Salesforce to insert internally) and updateUser (which updates an existing User record and performs an explicit update DML statement).

Below is an illustrative, runnable Apex class demonstrating defensive attribute parsing and profile lookup for internal workforce users. Note that this example was not executed in a live production org and requires testing and adaptation in a sandbox environment to match your specific SAML attribute keys, profile names, and org defaults before deployment.

Prerequisites and Execution Context

  • Where it runs: Assign this class to the SAML JIT Handler lookup in Setup → Identity → Single Sign-On Settings, and set Execute Handler As to an active System Administrator with Manage Users permission.
  • Important Mixed-DML & User Insertion Behavior: In createUser(), you populate fields on a new User() instance in memory and return it; Salesforce inserts the User record after createUser() finishes. Because the new User has no Id yet inside createUser(), and because inserting a portal Account/Contact alongside setup objects like PermissionSetAssignment can trigger MIXED_DML_OPERATION errors, any post-creation Permission Set assignments for brand-new users should be delegated to an asynchronous @future or Queueable method, or handled via an after-insert Record-Triggered Flow on the User object.
public class WorkforceCustomJitHandler implements Auth.SamlJitHandler {

    public class JitException extends Exception {}

    /**
     * Invoked on first SAML login when no User matches the FederationIdentifier.
     * Returns an instantiated User sObject; Salesforce performs the insert.
     */
    public User createUser(
        Id samlSsoProviderId, Id communityId, Id portalId,
        String federationIdentifier, Map<String, String> attributes, String assertion
    ) {
        User u = new User();
        u.FederationIdentifier = federationIdentifier;
        populateUserFields(u, attributes, true);
        return u;
    }

    /**
     * Invoked on subsequent SAML logins when an existing User matches the FederationIdentifier.
     * Must perform an explicit DML update on the User record.
     */
    public void updateUser(
        Id userId, Id samlSsoProviderId, Id communityId, Id portalId,
        String federationIdentifier, Map<String, String> attributes, String assertion
    ) {
        List<User> existingUsers = [
            SELECT Id, FirstName, LastName, Email, ProfileId, IsActive
            FROM User
            WHERE Id = :userId
            LIMIT 1
        ];

        if ( existingUsers.isEmpty() || !existingUsers[0].IsActive ) {
            throw new JitException('Cannot update inactive or missing User: ' + userId);
        }

        User u = existingUsers[0];
        populateUserFields(u, attributes, false);
        update u;
    }

    /**
     * Helper method to validate required SAML attributes and map defaults.
     */
    private void populateUserFields(User u, Map<String, String> attributes, Boolean isCreate) {
        if ( attributes == null || !attributes.containsKey('User.Email') || !attributes.containsKey('User.LastName') ) {
            throw new JitException('SAML assertion is missing required attributes: User.Email or User.LastName.');
        }

        String email = attributes.get('User.Email').trim();
        String lastName = attributes.get('User.LastName').trim();
        String firstName = attributes.containsKey('User.FirstName') ? attributes.get('User.FirstName').trim() : '';

        u.Email = email;
        u.LastName = lastName;
        u.FirstName = firstName;

        if ( isCreate ) {
            // Construct a unique username suffix or use assertion-supplied username
            u.Username = attributes.containsKey('User.Username') 
                ? attributes.get('User.Username').trim() 
                : email + '.jit.org';

            // Alias is max 8 characters and required on User creation
            String rawAlias = (String.isNotBlank(firstName) ? firstName.substring(0, 1) : 'u') + lastName;
            u.Alias = rawAlias.length() > 8 ? rawAlias.substring(0, 8) : rawAlias;

            // Required locale, language, and encoding defaults
            u.LanguageLocaleKey = 'en_US';
            u.LocaleSidKey = 'en_US';
            u.EmailEncodingKey = 'UTF-8';
            u.TimeZoneSidKey = attributes.containsKey('User.TimeZoneSidKey') 
                ? attributes.get('User.TimeZoneSidKey') 
                : 'America/Los_Angeles';
        }

        // Map Profile by name if supplied in assertion
        if ( attributes.containsKey('User.ProfileName') ) {
            String profName = attributes.get('User.ProfileName').trim();
            List<Profile> profs = [SELECT Id FROM Profile WHERE Name = :profName LIMIT 1];
            if ( profs.isEmpty() ) {
                throw new JitException('Mapped Profile not found in org: ' + profName);
            }
            u.ProfileId = profs[0].Id;
        } else if ( isCreate ) {
            throw new JitException('User.ProfileName attribute is required when creating a new JIT User.');
        }
    }
}

How to Verify and Troubleshoot JIT Provisioning

  1. Check Login History for Exact JIT Error Codes: When a SAML JIT login fails, open Setup → Identity → Login History. Salesforce logs specific JIT status messages (such as Failed: Unable to map unique profile or Failed: Custom JIT Handler Error).
  2. Run the SAML Assertion Validator: In Single Sign-On Settings, click SAML Assertion Validator. Paste the XML assertion or inspect the most recent failed login to confirm whether the <saml:AttributeStatement> actually contains the exact case-sensitive attribute names your Apex handler expects.
  3. Enable Debug Logs on the "Execute Handler As" User: Because Auth.SamlJitHandler runs under the designated administrative user specified in Single Sign-On Settings, setting a User Trace Flag on that admin user in Setup → Debug Logs captures any unhandled Apex exceptions or SOQL/DML errors thrown inside createUser or updateUser.

🧭 360 Card — JIT Provisioning & the Provisioning Spectrum

Rule: JIT creates and updates users at login, whereas SCIM manages the complete user lifecycle. Always pair JIT with an automated deprovisioning process because unmanaged leavers are where security and license risks accumulate.

Benefit: Thousands of workforce or partner portal users can onboard seamlessly without administrators pre-creating accounts or running manual CSV imports—the SAML assertion itself drives user creation.

When to Reach For Each: Reach for SCIM / REST API sync first whenever your Identity Provider supports it, because it automates Joiners, Movers, and Leavers continuously. Reach for SAML JIT when onboarding large external partner populations or when SCIM connectors are unavailable—and pair it with a scheduled deprovisioning job. Reserve Manual Creation strictly for break-glass administrators and small static teams.

Tradeoff: JIT is strictly reactive to login events; it has zero awareness when an employee or partner leaves the company.

Boundaries & Limits: Standard JIT requires rigid attribute names (User.*) and cannot run custom queries. Custom JIT uses an Auth.SamlJitHandler Apex class for complex profile, role, and account resolution, but must respect Apex governor limits and mixed-DML boundaries during login.

At Volume: Real-world JIT failures almost always cluster around two causes: a required User field (such as TimeZoneSidKey, Email, or Alias) missing from the IdP's assertion for a subset of directory users, or an invalid Profile/Role mapping for the target license type.

⚠ ARCHITECT & SECURITY TRAP: Never treat an OAuth 2.0 refresh_token as a harmless session cookie, and never assume that disabling SSO login kills existing API tokens. A refresh token is effectively a long-lived password that can generate new access tokens without touching the Identity Provider. Store refresh tokens exclusively in encrypted server-side storage (never in browser localStorage or client-side JavaScript), and ensure your user deprovisioning automation explicitly revokes OauthToken records and freezes or deactivates the User record.

❓ Core Q&A and Architecture Scenarios

Q: A partner organization needs to onboard thousands of external dealer users through their own corporate IdP. Pre-creating accounts in Salesforce is impractical. How do you provision them, and where does custom logic fit?
🎯 Say this first: Enable SAML 2.0 Just-In-Time (JIT) provisioning from the partner's IdP so users are created or updated automatically on their first login, and implement a Custom SAML JIT Handler (Auth.SamlJitHandler) in Apex to resolve their parent Dealer Account, Contact, Profile, and Permission Sets.

A: Here is how the end-to-end architecture works for external partner onboarding:

  • 1. Federated Trust Setup: Configure the partner's Identity Provider as a SAML 2.0 Single Sign-On setting tied to your Experience Cloud site, matching users on an immutable FederationIdentifier (such as the partner's unique user GUID).
  • 2. Why Custom JIT Beats Standard JIT for Portals: While Standard JIT can create portal users if the IdP sends exact Salesforce Account.Id or ProfileId values, an external partner IdP rarely knows internal 18-character Salesforce record IDs. Instead, the partner IdP sends business attributes in the SAML assertion—such as DealerCode=DLR-9042 and DealerRole=ServiceManager.
  • 3. Executing Custom Logic in Auth.SamlJitHandler: When the assertion arrives at first login, your custom Apex handler queries the Account object where External_Dealer_Code__c = 'DLR-9042', creates or updates the user's Contact under that exact Dealer Account, maps ServiceManager to the Partner Community User Profile, and triggers an asynchronous job or Record-Triggered Flow to assign granular Permission Sets.
  • 4. Closing the Lifecycle Loop: Because JIT only creates and updates users upon login, pair this onboarding design with either an automated nightly reconciliation batch or an API webhook from the partner directory to deactivate dealer users who leave the dealership.
Q1: SAML JIT provisioning works for most employees, but keeps failing for a specific subset of users. Where do you look first to troubleshoot?

A1: Intermittent JIT failures for a subset of users almost always point to inconsistent directory data in the SAML assertion or unhandled edge cases in the JIT handler:

  • Step 1 — Inspect Login History and the SAML Assertion Validator: Check Login History for the affected users to see the high-level failure reason, then capture their payload in the SAML Assertion Validator. Compare a failing user's <saml:AttributeStatement> side-by-side against a working user's assertion.
  • Step 2 — Check for Missing Required Directory Attributes: Often, a newly acquired subsidiary or contractor group in Active Directory is missing a field like Department, LastName, or TimeZone. If your JIT mapping requires that attribute and receives a null or blank string, the User DML operation fails.
  • Step 3 — Verify License and Profile/Role Compatibility: Confirm whether the assertion is mapping those users to a Profile or Role that no longer exists, is misspelled, or requires a User License that has run out of available seats in your org (check Setup → Company Information for license exhaustion).
  • Step 4 — Check for Duplicate Username or Federation ID Collisions: Remember that Username must be globally unique across all Salesforce production orgs worldwide. If Standard or Custom JIT sets Username = Email and the user already uses that email in another company's Salesforce org, user creation fails with a duplicate username error. Append an org-specific suffix (such as .yourcompany) to generated Username values while keeping Email as their actual email address.
Q2: JIT provisions and updates users cleanly—but during a security review, the CISO asks about "Leavers." What is the architectural gap, and how do you close it?

A2: The architectural gap is simple: JIT is strictly an event-driven login hook, not a full Identity Governance and Administration (IGA) lifecycle:

  • What Happens Without Deprovisioning: When an employee or partner is terminated in the IdP, they simply stop logging in via SSO. Because they never present a new SAML assertion to Salesforce, the JIT handler never runs to set IsActive = false. Their user record stays active, continues occupying a paid license seat, remains eligible to receive scheduled reports or automated emails, and retains any standing OAuth 2.0 refresh tokens or active sessions.
  • How to Close the Gap: Present JIT strictly as your Joiner-and-Mover mechanism (when SCIM is not already handling creation), and pair it with a dedicated Leaver mechanism:
    1. Option A (Preferred): Enable outbound SCIM provisioning from your IdP (Okta, Microsoft Entra ID, Ping) so deactivating or unassigning a user in the IdP immediately calls Salesforce's REST API to deactivate or freeze the User.
    2. Option B (When Partner IdPs Cannot Push SCIM): Build a scheduled reconciliation integration against the partner directory, or enforce an automated inactivity policy in Salesforce (via a Scheduled Flow or Batch Apex job querying LastLoginDate) that freezes or deactivates external users who have not logged in within a defined window (for example, 60 or 90 days).
Q3: Design an enterprise "Leaver" (deprovisioning) process end to end. What happens in what order?
🎯 Say this first: Because JIT never removes anyone, the Leaver workflow must be triggered upstream from HR and the Identity Provider via SCIM or API automation—freezing the user immediately, revoking active sessions and OAuth refresh tokens, transferring owned records and automation dependencies, and finally deactivating the User record to reclaim the license.

A3: A production-grade offboarding architecture executes five ordered steps—and separates immediate access cutoff from clean administrative teardown:

  1. Trigger from Authoritative Source (HR → IdP → Salesforce): When HR marks an employee or contractor as terminated, the HR system disables their corporate directory account. The IdP's SCIM connector (or an iPaaS webhook) immediately notifies Salesforce.
  2. Step 1 in Salesforce — Freeze Immediately (UserLogin.IsFrozen = true): In Salesforce, you cannot always set User.IsActive = false in a single API call if that user is currently the default workflow user, the running user of a dashboard, a customer portal administrator, or part of custom hierarchy fields. Therefore, your automation should first set IsFrozen = true on the UserLogin object. Freezing blocks login instantly without failing on record-ownership dependencies, though a frozen user still consumes a license until deactivated.
  3. Step 2 — Revoke Active Sessions and OAuth Refresh Tokens: Terminating the front-door login is not enough. Delete any active browser/API sessions in the AuthSession object for that UsersId, and revoke any standing third-party grants in OauthToken so mobile apps and connected scripts stop working immediately.
  4. Step 3 — Reassign Owned Records and Metadata Dependencies: Run an automated background job (or admin offboarding queue) to transfer open Leads, Accounts, Opportunities, and Cases to the leaver's manager, remove the user from Public Groups, Queues, and Approval Processes, and update any scheduled jobs or dashboards where they were the running user.
  5. Step 4 — Deactivate and Reclaim the License (User.IsActive = false): Once dependencies are cleared, set IsActive = false on the User record. This officially releases the Salesforce license back to your available pool. Finally, test this entire five-step chain prior to go-live using a synthetic test user so your security and audit teams have timestamped verification logs.

🧪 2-Minute Self-Check

Q1: Your SCIM integration attempts to set IsActive = false on a departing Regional Sales Director's Salesforce User record, but the API call fails because the user is selected as the running user on three executive dashboards and an approval step. As a result, the user remains active in Salesforce. How should you design your deprovisioning automation so administrative dependencies never delay cutting off a terminated user's access?

Answer & Explanation: Design a two-stage deprovisioning pattern: have the automated termination call set IsFrozen = true on the user's UserLogin record (and revoke their AuthSession and OauthToken records) first, before attempting User.IsActive = false. Freezing a user via UserLogin never fails due to dashboard running-user or workflow dependencies, guaranteeing immediate access cutoff while creating a support case for an admin to clean up the dependencies and complete deactivation to free the license.

Q2: Inside your Auth.SamlJitHandler.createUser() method, a developer writes insert new PermissionSetAssignment(AssigneeId = u.Id, PermissionSetId = psId); before returning u. Why will this code fail at runtime for every brand-new user?

Answer & Explanation: In createUser(), your code instantiates the User sObject in memory and returns it, and Salesforce performs the actual database insert after createUser() returns. Inside createUser(), u.Id is still null! To assign Permission Sets to newly created JIT users, move the PermissionSetAssignment insertion into an after-insert Record-Triggered Flow on User or an asynchronous @future / Queueable transaction.

🔗 Next Concept Follow-Up: Why Browser Apps Must Never Store Refresh Tokens

Follow-Up Q: In the summary, we noted that "a refresh token is a password—store it server-side, never in a browser." If you are building an external Single-Page Application (SPA) in React or Angular that calls Salesforce APIs, why is storing a refresh_token in browser localStorage dangerous, and what OAuth pattern should you use instead?

A: Any JavaScript running on the same origin—including a compromised third-party analytics script, an npm supply-chain dependency, or a single Cross-Site Scripting (XSS) vulnerability—can read window.localStorage and window.sessionStorage. Because a Salesforce OAuth 2.0 refresh_token is long-lived and can mint fresh access_token credentials silently without user interaction, exfiltrating a refresh token from a user's browser gives an attacker persistent offline access to your org.

Instead of exposing long-lived refresh tokens to browser JavaScript, use the OAuth 2.0 Authorization Code Flow with PKCE (Proof Key for Code Exchange) paired with short-lived access tokens and a Backend-for-Frontend (BFF) proxy (which stores the refresh token inside an encrypted, HttpOnly, Secure, SameSite server cookie inaccessible to JavaScript), or configure your External Client App / Connected App to expire refresh tokens aggressively upon inactivity and forbid issuing refresh tokens to untrusted public browser clients.

📚 Official Salesforce Documentation

To verify current release details, SAML attribute schemas, and Apex interface signatures, consult these primary Salesforce resources:

Was this article helpful?

Have a question or spotted a mistake? Ask here about this article

Found this guide useful? Buy me a coffee (opens in a new tab)

Comments

Questions, corrections and your own experience are all welcome.