Skip to main content

Unified Social Post Publishing Workflow - Complete Documentation

๐Ÿ“‹ Table of Contentsโ€‹


๐ŸŽฏ Overviewโ€‹

The Unified Social Post Publishing Workflow is a complete refactoring of the social media publishing system, reducing complexity from 351 lines to 67 lines (81% reduction) while improving maintainability, testability, and security.

Key Improvementsโ€‹

Before (Monolithic Route Handler):

  • โŒ 351 lines of business logic in route handler
  • โŒ Difficult to test individual components
  • โŒ Hard to modify or extend
  • โŒ Validation scattered throughout code
  • โŒ No clear separation of concerns

After (Modular Workflow):

  • โœ… 67-line route handler (thin HTTP wrapper)
  • โœ… 11 independently testable workflow steps
  • โœ… Clear separation of concerns
  • โœ… Easy to modify and extend
  • โœ… Centralized validation
  • โœ… Secure token management

๐Ÿ—๏ธ Architectureโ€‹

Workflow Flowโ€‹

POST /admin/social-posts/:id/publish
โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Route Handler (67 lines) โ”‚
โ”‚ - Validates request โ”‚
โ”‚ - Calls unified workflow โ”‚
โ”‚ - Returns response โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Unified Workflow (11 Steps) โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ 1. Load Post with Platform โ”‚
โ”‚ 2. Validate Platform โ”‚
โ”‚ 3. Decrypt Credentials โ”‚
โ”‚ 4. Detect Smart Retry โ”‚
โ”‚ 5. Extract Target Accounts โ”‚
โ”‚ 6. Extract Content โ”‚
โ”‚ 7. Determine Content Type โ”‚
โ”‚ 8. Validate Content Compatibility โ”‚
โ”‚ 9. Route to Platform Workflow โ”‚
โ”‚ 10. Merge Publish Results โ”‚
โ”‚ 11. Update Post with Results โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Platform-Specific Workflows โ”‚
โ”‚ - Twitter Workflow โ”‚
โ”‚ - Facebook/Instagram Workflow โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Directory Structureโ€‹

src/
โ”œโ”€โ”€ api/admin/social-posts/[id]/publish/
โ”‚ โ”œโ”€โ”€ route.ts # 67-line route handler
โ”‚ โ””โ”€โ”€ validators.ts # Request validation
โ”‚
โ”œโ”€โ”€ workflows/socials/
โ”‚ โ”œโ”€โ”€ publish-social-post-unified.ts # Main workflow
โ”‚ โ”‚
โ”‚ โ””โ”€โ”€ steps/
โ”‚ โ”œโ”€โ”€ index.ts # Export all steps
โ”‚ โ”œโ”€โ”€ load-post-with-platform.ts # Step 1
โ”‚ โ”œโ”€โ”€ validate-platform.ts # Step 2
โ”‚ โ”œโ”€โ”€ decrypt-credentials.ts # Step 3
โ”‚ โ”œโ”€โ”€ detect-smart-retry.ts # Step 4
โ”‚ โ”œโ”€โ”€ extract-target-accounts.ts # Step 5
โ”‚ โ”œโ”€โ”€ extract-content.ts # Step 6
โ”‚ โ”œโ”€โ”€ determine-content-type.ts # Step 7
โ”‚ โ”œโ”€โ”€ validate-content-compatibility.ts # Step 8
โ”‚ โ”œโ”€โ”€ route-to-platform-workflow.ts # Step 9
โ”‚ โ”œโ”€โ”€ merge-publish-results.ts # Step 10
โ”‚ โ””โ”€โ”€ update-post-with-results.ts # Step 11
โ”‚
โ””โ”€โ”€ modules/socials/utils/
โ””โ”€โ”€ token-helpers.ts # Token encryption/decryption

๐Ÿ“ Workflow Stepsโ€‹

Step 1: Load Post with Platformโ€‹

File: load-post-with-platform.ts

Purpose: Loads the social post by ID with its associated platform.

Input:

{ post_id: string }

Output:

{ 
post: SocialPost,
platform: SocialPlatform
}

Validation:

  • Post exists
  • Platform is associated with post

Step 2: Validate Platformโ€‹

File: validate-platform.ts

Purpose: Validates platform configuration and status.

Input:

{ platform: SocialPlatform }

Output:

{ 
platform_name: string,
platform_category: string
}

Validation:

  • Platform is active
  • Platform has required configuration

Step 3: Decrypt Credentialsโ€‹

File: decrypt-credentials.ts

Purpose: Securely decrypts OAuth tokens from encrypted storage.

Input:

{ 
platform: SocialPlatform,
platform_name: string
}

Output:

{ 
decrypted_token: string,
api_config: Record<string, unknown>
}

Security:

  • Uses AES-256-GCM encryption
  • Tokens never logged in plaintext
  • Supports key rotation

Step 4: Detect Smart Retryโ€‹

File: detect-smart-retry.ts

Purpose: Implements smart retry logic - only retry failed platforms.

Input:

{ 
post: SocialPost,
platform_name: string
}

Output:

{ 
is_retry: boolean,
target_platforms: string[],
previous_results: PublishResult[]
}

Logic:

  • Checks post.insights.publish_results
  • For FBINSTA: Only retry failed platform (Facebook OR Instagram)
  • For single platforms: Retry if failed

Step 5: Extract Target Accountsโ€‹

File: extract-target-accounts.ts

Purpose: Extracts account IDs from post metadata or overrides.

Input:

{ 
post: SocialPost,
platform_name: string,
override_page_id?: string,
override_ig_user_id?: string
}

Output:

{ 
page_id?: string,
ig_user_id?: string
}

Validation:

  • Facebook requires page_id
  • Instagram requires ig_user_id
  • FBINSTA requires both

Step 6: Extract Contentโ€‹

File: extract-content.ts

Purpose: Extracts caption and media from post.

Input:

{ post: SocialPost }

Output:

{ 
caption: string,
media_attachments: Record<string, MediaAttachment>
}

Step 7: Determine Content Typeโ€‹

File: determine-content-type.ts

Purpose: Analyzes media and determines content type.

Input:

{ media_attachments: Record<string, MediaAttachment> }

Output:

{ 
content_type: "photo" | "video" | "carousel" | "text"
}

Logic:

  • No media โ†’ text
  • 1 image โ†’ photo
  • 1 video โ†’ video
  • Multiple images โ†’ carousel

Step 8: Validate Content Compatibilityโ€‹

File: validate-content-compatibility.ts

Purpose: Validates content against platform-specific rules.

Input:

{ 
platform_name: string,
content_type: string,
caption: string,
media_count: number
}

Validation Rules:

Instagram:

  • โŒ Text-only posts not supported
  • โœ… Photo, video, carousel supported

Twitter:

  • โœ… Max 280 characters
  • โœ… Max 4 images
  • โœ… Text-only supported

Facebook:

  • โœ… All content types supported

Step 9: Route to Platform Workflowโ€‹

File: route-to-platform-workflow.ts

Purpose: Routes to appropriate platform-specific workflow.

Input:

{ 
platform_name: string,
post: SocialPost,
// ... all previous step outputs
}

Routing Logic:

  • twitter โ†’ publishSocialPostWorkflow (Twitter)
  • facebook, instagram, fbinsta โ†’ publishToBothPlatformsUnifiedWorkflow

Output:

{ 
facebook?: PublishResult,
instagram?: PublishResult,
twitter?: PublishResult
}

Step 10: Merge Publish Resultsโ€‹

File: merge-publish-results.ts

Purpose: Merges new results with previous attempts (for smart retry).

Input:

{ 
new_results: PublishResults,
previous_results: PublishResult[],
is_retry: boolean
}

Output:

{ 
merged_results: PublishResult[]
}

Logic:

  • First attempt: Use new results
  • Retry: Merge with previous, keeping successful results

Step 11: Update Post with Resultsโ€‹

File: update-post-with-results.ts

Purpose: Updates post with publish results and status.

Input:

{ 
post: SocialPost,
merged_results: PublishResult[],
is_retry: boolean,
platform_name: string
}

Updates:

  • status: published or failed
  • posted_at: Current timestamp (if successful)
  • insights.publish_results: Merged results
  • insights.facebook_post_id: Facebook post ID
  • insights.instagram_media_id: Instagram media ID
  • insights.twitter_tweet_id: Twitter tweet ID
  • error_message: Error details (if failed)

Output:

{ 
success: boolean,
updated_post: SocialPost,
results: PublishResults,
retry_info?: RetryInfo
}

๐Ÿ”Œ API Endpointsโ€‹

POST /admin/social-posts/:id/publishโ€‹

Publishes a social media post to configured platforms.

Request:

POST /admin/social-posts/post_123/publish
Content-Type: application/json
Authorization: Bearer <admin_token>

{
"override_page_id": "987654321", // Optional
"override_ig_user_id": "123456789" // Optional
}

Response (Success):

{
"success": true,
"post": {
"id": "post_123",
"status": "published",
"posted_at": "2025-11-19T14:00:00Z",
// ... other post fields
},
"results": {
"facebook": {
"success": true,
"post_id": "fb_post_123",
"url": "https://facebook.com/..."
},
"instagram": {
"success": true,
"media_id": "ig_media_456",
"url": "https://instagram.com/..."
}
},
"retry_info": {
"is_retry": false,
"retried_platforms": []
}
}

Response (Failure):

{
"success": false,
"post": {
"id": "post_123",
"status": "failed",
"error_message": "Publishing failed: Invalid OAuth access token"
},
"results": {
"facebook": {
"success": false,
"error": "Invalid OAuth access token"
}
}
}

Error Codes:

  • 400 - Validation error (missing page_id, invalid content, etc.)
  • 404 - Post not found
  • 500 - Server error

๐Ÿ’ก Usage Examplesโ€‹

Example 1: Publish to Facebookโ€‹

// Create a post
const post = await api.post("/admin/social-posts", {
name: "My Facebook Post",
caption: "Hello Facebook! #test",
status: "draft",
platform_id: "facebook_platform_id",
media_attachments: {
"0": {
type: "image",
url: "https://example.com/image.jpg"
}
},
metadata: {
page_id: "123456789",
publish_target: "facebook"
}
})

// Publish it
const result = await api.post(`/admin/social-posts/${post.id}/publish`, {})

Example 2: Publish to Both Facebook & Instagramโ€‹

const post = await api.post("/admin/social-posts", {
name: "My FBINSTA Post",
caption: "Hello both platforms!",
status: "draft",
platform_id: "fbinsta_platform_id",
media_attachments: {
"0": {
type: "image",
url: "https://example.com/image.jpg"
}
},
metadata: {
page_id: "123456789",
ig_user_id: "987654321",
publish_target: "both"
}
})

const result = await api.post(`/admin/social-posts/${post.id}/publish`, {})

Example 3: Smart Retry (Only Failed Platform)โ€‹

// First attempt - Facebook succeeds, Instagram fails
const firstAttempt = await api.post(`/admin/social-posts/${post.id}/publish`, {})
// Result: { facebook: { success: true }, instagram: { success: false } }

// Retry - Only retries Instagram
const retryAttempt = await api.post(`/admin/social-posts/${post.id}/publish`, {})
// Result: { facebook: { success: true }, instagram: { success: true } }
// Facebook result preserved from first attempt!

Example 4: Override Account IDsโ€‹

// Override page_id at publish time
const result = await api.post(`/admin/social-posts/${post.id}/publish`, {
override_page_id: "different_page_id",
override_ig_user_id: "different_ig_user_id"
})

๐Ÿงช Testingโ€‹

Integration Testsโ€‹

File: /integration-tests/http/socials/unified-publish-workflow.spec.ts

Test Coverage:

  1. โœ… Workflow execution with fake tokens (expects Facebook API error)
  2. โœ… Validation of missing page_id
  3. โœ… Rejection of array format for media_attachments

Run Tests:

yarn test:integration:http ./integration-tests/http/socials/unified-publish-workflow.spec.ts

Test Strategyโ€‹

What We Test:

  • โœ… Workflow structure and step execution
  • โœ… Validation logic (page_id, content compatibility, etc.)
  • โœ… Error handling and messaging
  • โœ… Data format validation

What We Don't Test (requires real OAuth tokens):

  • โŒ Actual publishing to Facebook/Instagram/Twitter
  • โŒ Real API responses
  • โŒ Live token validation

For E2E Testing (manual/staging):

  • Set up test social media accounts
  • Use real OAuth tokens
  • Verify posts actually appear on platforms

๐Ÿš€ Deploymentโ€‹

Prerequisitesโ€‹

  1. Encryption Keys (if using token encryption)

    # Generate encryption key
    openssl rand -hex 32

    # Add to environment
    ENCRYPTION_KEY=your_generated_key
  2. Database Migration (if schema changed)

    yarn medusa migrations run

Deployment Stepsโ€‹

  1. Staging Deployment

    # Deploy to staging
    git push staging main

    # Monitor logs
    tail -f /var/log/medusa/staging.log

    # Test publishing
    curl -X POST https://staging.example.com/admin/social-posts/test_id/publish \
    -H "Authorization: Bearer $STAGING_TOKEN"
  2. Production Deployment

    # Deploy to production
    git push production main

    # Monitor metrics
    # - Response times
    # - Error rates
    # - Success rates
  3. Rollback Plan

    # If issues occur, rollback
    git revert HEAD
    git push production main

๐Ÿ”ง Troubleshootingโ€‹

Common Issuesโ€‹

1. "Invalid OAuth access token"โ€‹

Cause: Token is invalid or expired
Solution: Re-authenticate the platform

2. "No Facebook page_id found"โ€‹

Cause: Missing page_id in post metadata
Solution: Add page_id to metadata or provide override_page_id

3. "Text-only posts are not supported on Instagram"โ€‹

Cause: Instagram requires media
Solution: Add at least one image or video

4. "Tweet exceeds 280 characters"โ€‹

Cause: Caption too long for Twitter
Solution: Shorten caption to 280 characters or less

5. "Expected type: 'object' for field 'media_attachments', got: 'array'"โ€‹

Cause: Using array format instead of object
Solution: Change media_attachments from [] to {}

// โŒ Wrong
media_attachments: [
{ type: "image", url: "..." }
]

// โœ… Correct
media_attachments: {
"0": { type: "image", url: "..." }
}

Debug Modeโ€‹

Enable detailed logging:

// Each workflow step logs its progress
// Check console for step-by-step execution:
// [Load Post] โœ“ Loaded post post_123 with platform Facebook
// [Validate Platform] โœ“ Platform Facebook is active
// [Decrypt Credentials] โœ“ Access token decrypted successfully
// ...

๐Ÿ“Š Metrics & Monitoringโ€‹

Key Metrics to Trackโ€‹

  1. Success Rate: % of successful publishes
  2. Error Rate: % of failed publishes
  3. Response Time: Average time to publish
  4. Retry Rate: % of posts that needed retry
  5. Platform-Specific Success: Success rate per platform

Monitoring Setupโ€‹

// Example monitoring with custom metrics
const publishMetrics = {
total_attempts: 0,
successful: 0,
failed: 0,
retries: 0,
avg_response_time: 0
}

// Track in workflow
publishMetrics.total_attempts++
if (result.success) publishMetrics.successful++
else publishMetrics.failed++

๐ŸŽ“ Best Practicesโ€‹

  1. Always Use Object Format for media_attachments

    media_attachments: { "0": {...}, "1": {...} }
  2. Provide Required Account IDs

    • Facebook: page_id
    • Instagram: ig_user_id
    • FBINSTA: Both
  3. Handle Errors Gracefully

    • Check result.success before assuming success
    • Display error messages to users
    • Implement retry logic for transient failures
  4. Test Before Production

    • Use staging environment
    • Test all platforms
    • Verify content appears correctly
  5. Monitor Production

    • Track success/failure rates
    • Set up alerts for high error rates
    • Monitor API rate limits

๐Ÿ“š Additional Resourcesโ€‹


๐Ÿค Supportโ€‹

For issues or questions:

  1. Check Troubleshooting section
  2. Review integration tests for examples
  3. Check workflow step logs for detailed execution flow
  4. Contact development team

Last Updated: November 19, 2025
Version: 1.0.0
Status: โœ… Production Ready