Skip to content

Social publishing recipes

All examples use ordinary Call Baxter rules. Adjust plugin credentials and Telegram attribute paths to the ingress/projection contract in your deployment.

Mirror one Telegram message to all three providers

id: mirror-social-all
enabled: true
match:
  source: telegram
  kind: telegram.message.new
actions:
  - kind: bluesky.mirror_telegram_message
    args:
      update: "{{ event.attrs.raw_update }}"
  - kind: mastodon.mirror_telegram_message
    args:
      update: "{{ event.attrs.raw_update }}"
  - kind: x.mirror_telegram_message
    args:
      update: "{{ event.attrs.raw_update }}"

The three effects are independent. If one provider fails after another succeeded, Call Baxter records partial success; there is no cross-provider transaction.

Require an explicit publishing hashtag

id: mirror-social-tagged
enabled: true
match:
  source: telegram
  kind: telegram.message.new
actions:
  - kind: mastodon.mirror_telegram_message
    args:
      update: "{{ event.attrs.raw_update }}"
      required_hashtag: "#publish"

The shared mirror contract compares hashtags case-insensitively. A message without the required tag returns a successful mastodon.skipped result with reason required_hashtag_missing; it is not a provider failure.

Stage Telegram media groups

Configure a non-zero wait and a durable state database:

plugins:
  - module: cb_social_plugin.bluesky
    config:
      identifier: "publisher.example"
      app_password: "${BLUESKY_APP_PASSWORD}"
      state_db_path: "/srv/data/social-publications.db"
      media_group_wait_seconds: 2

The ordinary mirror action becomes the staging path for messages carrying media_group_id. Do not add a second broad “album staging” mirror rule. Run bluesky.flush_due_media_groups periodically after the wait window; equivalent flush actions exist for Mastodon and X.

actions:
  - kind: bluesky.flush_due_media_groups

A dry-run flush reports due publication keys without deleting staged rows. A successful real flush publishes once, persists the mapping, and removes the staged group.

Propagate Telegram edits

Use a second rule for the edit event kind and point it at the same provider/state database:

id: mirror-social-edits
enabled: true
match:
  source: telegram
  kind: telegram.message.edited
actions:
  - kind: bluesky.mirror_telegram_message
    args:
      update: "{{ event.attrs.raw_update }}"
  - kind: mastodon.mirror_telegram_message
    args:
      update: "{{ event.attrs.raw_update }}"
  - kind: x.mirror_telegram_message
    args:
      update: "{{ event.attrs.raw_update }}"
      edit_policy: native

Bluesky updates the deterministic record key, Mastodon edits the mapped status, and this rule opts X into its native edit chain. X's compatibility default remains edit_policy: ignore; existing edit_policy: replace rules retain delete-then-repost behavior.

For production X mirroring, use a persistent state_db_path. When a prior X request has an ambiguous remote outcome, the next delivery reconciles the stored attempt and recent timeline rather than sending a second create blindly; unresolved ambiguity fails closed with XMirrorRecoveryRequired. Stateless compatibility mode cannot provide that recovery guarantee.

Fail the chain when media is mandatory

actions:
  - kind: mastodon.mirror_telegram_message
    args:
      update: "{{ event.attrs.raw_update }}"
      media_required: true
    on_error:
      - kind: telegram.send_message
        args:
          chat_id: 123456
          text: "Social publication failed because required media could not be acquired."

media_required converts media acquisition omissions into action failures, which activates normal on_error behavior.