Frog Wizard

Automating Social Media Posts with Trello

I wanted a way to queue up social media posts from my phone or the web and have them go out on a schedule without paying for anything. Trello is free and has an API, so I used a Trello board as a post queue. No need to build a front end.

What It Does

You create Trello cards on a designated list. The card title becomes your post text. Attach an image and the card description becomes its alt text. Run it as a daemon and it pulls cards off the list and posts them to Twitter/X, Bluesky, and Mastodon.

No web interface or database needed.

How It Works

The core loop is straightforward — grab a card, turn it into a post, send it everywhere, delete the card:

def post_job(trello: TrelloClient, posters: list) -> None:
    cards = trello.get_cards(limit=1)
    if not cards:
        log.info("no cards available to post")
        return

    card = cards[0]
    post = trello.card_to_post(card)
    log.info("posting card: %s", card.name)

    results = post_to_all_platforms(post, posters)

    if any(r.success for r in results):
        trello.delete_card(card)

A card gets converted into a CardPost dataclass that each platform poster knows how to handle. If a card has an image attachment, the image is downloaded and bundled with the post:

@dataclass
class CardPost:
    text: str
    image_bytes: bytes | None = None
    image_mime: str | None = None
    alt_text: str | None = None

Failures on one platform don't block others — if Bluesky is down but Twitter works, the card still gets posted and deleted. Posting to at least one platform is good enough for my use case.

Scheduling

Post times and randomization are configured in TOML:

[schedule]
post_times = ["09:00", "13:00", "18:30"]
post_time_randomization = 600  # +/- seconds

Under the hood it uses APScheduler with cron triggers. The randomization offsets each job's trigger time and adds jitter so posts don't land at the exact same second every day:

for time_str in cfg.schedule.post_times:
    hour, minute = time_str.split(":")
    base = datetime(2000, 1, 1, int(hour), int(minute))
    shifted = base - timedelta(seconds=r)

    job_kwargs = dict(
        func=post_job,
        trigger=CronTrigger(
            hour=shifted.hour, minute=shifted.minute, second=shifted.second,
        ),
        args=[trello, posters],
    )
    if r > 0:
        job_kwargs["jitter"] = r * 2

    scheduler.add_job(**job_kwargs)

Setup

Install with pipx, copy the example config to ~/.config/trello-post-scheduler/config.toml, and fill in your API keys. You can run it as a daemon via the included systemd user service:

[Service]
Type=simple
ExecStart=%h/.local/bin/trello-post-scheduler --config %h/.config/trello-post-scheduler/config.toml
Restart=on-failure
RestartSec=30

Or trigger it with your own cron scheduler using --once to post a single card and exit.

The Repo

Source code and setup instructions: github.com/tylerhenthorn/trello-post-scheduler