Files
igny8/docs/40-WORKFLOWS/SCHEDULED-CONTENT-PUBLISHING.md
2026-01-12 23:25:47 +00:00

15 KiB

Scheduled Content Publishing Workflow

Last Updated: January 12, 2026
Module: Publishing / Automation


Overview

IGNY8 provides automated content publishing to WordPress sites. Content goes through a scheduling process before being published at the designated time.


Content Lifecycle for Publishing

Understanding Content.status vs Content.site_status

Content has TWO separate status fields:

  1. status - Editorial workflow status

    • draft - Being created/edited
    • review - Submitted for review
    • approved - Ready for publishing
    • published - Legacy (not used for external publishing)
  2. site_status - External site publishing status

    • not_published - Not yet published to WordPress
    • scheduled - Has a scheduled_publish_at time
    • publishing - Currently being published
    • published - Successfully published to WordPress
    • failed - Publishing failed

Publishing Flow

┌─────────────────┐
│     DRAFT       │  ← Content is being created/edited
│  status: draft  │
└────────┬────────┘
         │ User approves content
         ▼
┌─────────────────┐
│    APPROVED     │  ← Content is ready for publishing
│ status: approved│     status='approved', site_status='not_published'
│ site_status:    │
│  not_published  │
└────────┬────────┘
         │ Hourly: schedule_approved_content task
         ▼
┌─────────────────┐
│   SCHEDULED     │  ← Content has a scheduled_publish_at time
│ status: approved│     site_status='scheduled'
│ site_status:    │     scheduled_publish_at set to future datetime
│    scheduled    │
└────────┬────────┘
         │ Every 5 min: process_scheduled_publications task
         │ (when scheduled_publish_at <= now)
         ▼
┌─────────────────┐
│   PUBLISHING    │  ← WordPress API call in progress
│ status: approved│     site_status='publishing'
│ site_status:    │
│   publishing    │
└────────┬────────┘
         │
    ┌────┴────┐
    │         │
    ▼         ▼
┌────────┐  ┌────────┐
│PUBLISHED│  │ FAILED │
│status:  │  │status: │
│approved │  │approved│
│site_    │  │site_   │
│status:  │  │status: │
│published│  │failed  │
└─────────┘  └────────┘

Celery Tasks

1. schedule_approved_content

Schedule: Every hour at :00
Task Name: publishing.schedule_approved_content
File: backend/igny8_core/tasks/publishing_scheduler.py

What It Does:

  1. Finds all sites with PublishingSettings.auto_publish_enabled = True
  2. Gets approved content (status='approved', site_status='not_published', scheduled_publish_at=null)
  3. Calculates available publishing slots based on:
    • publish_days - which days are allowed (e.g., Mon-Fri)
    • publish_time_slots - which times are allowed (e.g., 09:00, 14:00, 18:00)
    • daily_publish_limit - max posts per day
    • weekly_publish_limit - max posts per week
    • monthly_publish_limit - max posts per month
  4. Assigns scheduled_publish_at datetime and sets site_status='scheduled'

Configuration Location:

PublishingSettings model linked to each Site. Configurable via:

  • Admin: /admin/integration/publishingsettings/
  • API: /api/v1/sites/{site_id}/publishing-settings/

2. process_scheduled_publications

Schedule: Every 5 minutes
Task Name: publishing.process_scheduled_publications
File: backend/igny8_core/tasks/publishing_scheduler.py

What It Does:

  1. Finds all content where:
    • site_status='scheduled'
    • scheduled_publish_at <= now
  2. For each content item:
    • Updates site_status='publishing'
    • Gets the site's WordPress integration
    • Queues publish_content_to_wordpress Celery task
  3. Logs results and any errors

3. publish_content_to_wordpress

Type: On-demand Celery task (queued by process_scheduled_publications)
Task Name: publishing.publish_content_to_wordpress
File: backend/igny8_core/tasks/wordpress_publishing.py

What It Does:

  1. Load Content & Integration - Gets content and WordPress credentials
  2. Check Already Published - Skips if external_id exists
  3. Generate Excerpt - Creates excerpt from HTML content
  4. Get Taxonomy Terms - Loads categories and tags from ContentTaxonomy
  5. Get Images - Loads featured image and gallery images
  6. Build API Payload - Constructs WordPress REST API payload
  7. Call WordPress API - POSTs to WordPress via IGNY8 Bridge plugin
  8. Update Content - Sets external_id, external_url, site_status='published'
  9. Log Sync Event - Records in SyncEvent model

WordPress Connection:

  • Uses the IGNY8 WordPress Bridge plugin installed on the site
  • API endpoint: {site_url}/wp-json/igny8-bridge/v1/publish
  • Authentication: API key stored in Site.wp_api_key

Database Models

Field Type Description
status CharField Editorial workflow: draft, review, approved
site_status CharField WordPress publishing status: not_published, scheduled, publishing, published, failed
site_status_updated_at DateTimeField When site_status was last changed
scheduled_publish_at DateTimeField When content should be published (null if not scheduled)
external_id CharField WordPress post ID after publishing
external_url URLField WordPress post URL after publishing

Important: These are separate concerns:

  • status tracks editorial approval
  • site_status tracks external publishing
  • Content typically has status='approved' AND site_status='not_published' before scheduling

PublishingSettings Fields

Field Type Description
site ForeignKey The site these settings apply to
auto_publish_enabled BooleanField Whether automatic scheduling is enabled
publish_days JSONField List of allowed days: ['mon', 'tue', 'wed', 'thu', 'fri']
publish_time_slots JSONField List of times: ['09:00', '14:00', '18:00']
daily_publish_limit IntegerField Max posts per day (null = unlimited)
weekly_publish_limit IntegerField Max posts per week (null = unlimited)
monthly_publish_limit IntegerField Max posts per month (null = unlimited)

Celery Beat Schedule

From backend/igny8_core/celery.py:

app.conf.beat_schedule = {
    # ...
    'schedule-approved-content': {
        'task': 'publishing.schedule_approved_content',
        'schedule': crontab(minute=0),  # Every hour at :00
    },
    'process-scheduled-publications': {
        'task': 'publishing.process_scheduled_publications',
        'schedule': crontab(minute='*/5'),  # Every 5 minutes
    },
    # ...
}

Manual Publishing

Content can also be published immediately via:

API Endpoint

POST /api/v1/content/{content_id}/publish/

Admin Action

In Django Admin, select content and use "Publish to WordPress" action.


Monitoring & Debugging

Log Files

  • Publish Logs: backend/logs/publish-sync-logs/
  • API Logs: backend/logs/wordpress_api.log

Check Celery Status

docker compose -f docker-compose.app.yml -p igny8-app logs igny8_celery_worker
docker compose -f docker-compose.app.yml -p igny8-app logs igny8_celery_beat

Check Scheduled Content

# Django shell
from igny8_core.business.content.models import Content
from django.utils import timezone

# Past due content (should have been published)
Content.objects.filter(
    site_status='scheduled',
    scheduled_publish_at__lt=timezone.now()
).count()

# Upcoming scheduled content
Content.objects.filter(
    site_status='scheduled',
    scheduled_publish_at__gt=timezone.now()
).order_by('scheduled_publish_at')[:10]

Manual Task Execution

# Django shell
from igny8_core.tasks.publishing_scheduler import (
    schedule_approved_content,
    process_scheduled_publications
)

# Run scheduling task
schedule_approved_content()

# Process due publications
process_scheduled_publications()

Error Handling

Common Failure Reasons

Error Cause Solution
No active WordPress integration Site doesn't have WordPress connected Configure integration in Site settings
API key invalid/expired WordPress API key issue Regenerate API key in WordPress plugin
Connection timeout WordPress site unreachable Check site availability
Plugin not active IGNY8 Bridge plugin disabled Enable plugin in WordPress
Content already published Duplicate publish attempt Check external_id field

Retry Policy

  • publish_content_to_wordpress has max_retries=3
  • Automatic retry on transient failures
  • Failed content marked with site_status='failed'

Troubleshooting

Content Not Being Scheduled

  1. Check PublishingSettings.auto_publish_enabled is True
  2. Verify content has status='approved' and site_status='not_published'
  3. Check scheduled_publish_at is null (already scheduled content won't reschedule)
  4. Verify publish limits haven't been reached

Content Not Publishing

  1. Check Celery Beat is running: docker compose logs igny8_celery_beat
  2. Check Celery Worker is running: docker compose logs igny8_celery_worker
  3. Look for errors in worker logs
  4. Verify WordPress integration is active
  5. Test WordPress API connectivity

Resetting Failed Content

# Reset failed content to try again
from igny8_core.business.content.models import Content

Content.objects.filter(site_status='failed').update(
    site_status='not_published',
    scheduled_publish_at=None
)

Architecture Diagram

┌─────────────────────────────────────────────────────────────────┐
│                         IGNY8 Backend                           │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌──────────────────┐    ┌──────────────────┐                  │
│  │   Celery Beat    │    │  Celery Worker   │                  │
│  │                  │    │                  │                  │
│  │ Sends tasks at   │───▶│ Executes tasks   │                  │
│  │ scheduled times  │    │                  │                  │
│  └──────────────────┘    └────────┬─────────┘                  │
│                                   │                             │
│                                   ▼                             │
│  ┌────────────────────────────────────────────────────────┐    │
│  │              Publishing Tasks                           │    │
│  │                                                        │    │
│  │  1. schedule_approved_content (hourly)                 │    │
│  │     - Find approved content                            │    │
│  │     - Calculate publish slots                          │    │
│  │     - Set scheduled_publish_at                         │    │
│  │                                                        │    │
│  │  2. process_scheduled_publications (every 5 min)       │    │
│  │     - Find due content                                 │    │
│  │     - Queue publish_content_to_wordpress               │    │
│  │                                                        │    │
│  │  3. publish_content_to_wordpress                       │    │
│  │     - Build API payload                                │    │
│  │     - Call WordPress REST API                          │    │
│  │     - Update content status                            │    │
│  └────────────────────────────────────────────────────────┘    │
│                                   │                             │
└───────────────────────────────────┼─────────────────────────────┘
                                    │
                                    ▼ HTTPS
┌───────────────────────────────────────────────────────────────┐
│                    WordPress Site                              │
├───────────────────────────────────────────────────────────────┤
│  ┌─────────────────────────────────────────────────────────┐  │
│  │              IGNY8 Bridge Plugin                         │  │
│  │                                                         │  │
│  │  /wp-json/igny8-bridge/v1/publish                       │  │
│  │  - Receives content payload                             │  │
│  │  - Creates/updates WordPress post                       │  │
│  │  - Handles images, categories, tags                     │  │
│  │  - Returns post ID and URL                              │  │
│  └─────────────────────────────────────────────────────────┘  │
└───────────────────────────────────────────────────────────────┘