|
| 1 | +# Keynoter Social Media Scheduling |
| 2 | + |
| 3 | +This document describes the process for scheduling keynote speaker |
| 4 | +announcement posts to Buffer. In short, you should: |
| 5 | +* prepare the images in canva and deploy them to have a live url to the image |
| 6 | +* prepare copy for each platform and put it into a json file |
| 7 | +* run the script to schedule posts for the keynoters, one at a time, to all the supported social media platform |
| 8 | + |
| 9 | +The script posts to all connected channels in one run per keynoter: |
| 10 | +`instagram`, `linkedin`, `fosstodon`, `bsky`, `x`, `tiktok`. |
| 11 | + |
| 12 | +--- |
| 13 | + |
| 14 | +## Prerequisites |
| 15 | + |
| 16 | +- Python environment with `requests` and `python-dotenv` installed |
| 17 | +- A `.env.local` file in the **repo root** with your Buffer API key: |
| 18 | + |
| 19 | +``` |
| 20 | +BUFFER_API_KEY=your_buffer_api_key_here |
| 21 | +``` |
| 22 | + |
| 23 | + |
| 24 | +### Getting the Buffer API key |
| 25 | + |
| 26 | +1. Log in to [buffer.com](https://buffer.com) with the EuroPython account |
| 27 | +2. Go to **Account Settings → Apps & Integrations** |
| 28 | +3. Copy the personal access token under **Access Token** |
| 29 | +4. Paste it as `BUFFER_API_KEY` in `.env.local` |
| 30 | + |
| 31 | + |
| 32 | +--- |
| 33 | + |
| 34 | +## Step 1 — Prepare and deploy the images |
| 35 | + |
| 36 | +Buffer requires a **publicly accessible image URL** — local file paths, Google |
| 37 | +Drive links, and Canva share links do not work. The image must be hosted on the |
| 38 | +live site before the script can use it. |
| 39 | + |
| 40 | +### Prepare the images |
| 41 | + |
| 42 | +- Export images as PNG from your design tool (Canva) |
| 43 | +- Name them `firstname-lastname.png` (lowercase, hyphenated) |
| 44 | +- Place them in `website/public/media/keynoters/` |
| 45 | + |
| 46 | +### Deploy |
| 47 | + |
| 48 | +1. Commit the images and open a PR |
| 49 | +2. Wait for the PR to be merged and deployed to production |
| 50 | +3. Verify each image is accessible, e.g.: |
| 51 | + `https://ep2026.europython.eu/media/keynoters/leah-wasser.png` |
| 52 | + |
| 53 | +> ⚠️ Do not run the scheduling script until the images are live. Buffer fetches |
| 54 | +> the URL at scheduling time and will fail with a "Not Found" error if the file |
| 55 | +> hasn't been deployed yet. |
| 56 | +
|
| 57 | +## Step 2 — Prepare the post copy (`keynoters.json`) |
| 58 | + |
| 59 | +All post text lives in `keynoters.json` |
| 60 | + |
| 61 | +```json |
| 62 | +{ |
| 63 | + "Speaker Name": { |
| 64 | + "image": "https://ep<year>.europython.eu/media/keynoters/firstname-lastname.png", |
| 65 | + "instagram": "Post text for Instagram...", |
| 66 | + "linkedin": "Post text for LinkedIn...", |
| 67 | + "fosstodon": "Post text for Fosstodon/Mastodon...", |
| 68 | + "bsky": "Post text for Bluesky...", |
| 69 | + "x": "Post text for X/Twitter...", |
| 70 | + "tiktok": "Post text for TikTok..." |
| 71 | + } |
| 72 | +} |
| 73 | +``` |
| 74 | + |
| 75 | +A few things to keep in mind when writing copy: |
| 76 | + |
| 77 | +- Social handles (e.g. `@leahawasser.bsky.social`) go **inline in the post text**, |
| 78 | + not as separate fields. Each platform's post should use the handle format native |
| 79 | + to that platform. |
| 80 | +- X and Bluesky have character limits — keep those posts short. |
| 81 | +- Instagram and TikTok don't render clickable URLs, so use the short form |
| 82 | + (`europython.eu/tickets/`) rather than the full URL. |
| 83 | +- LinkedIn and Fosstodon support full clickable URLs. |
| 84 | + |
| 85 | +Add one entry per keynoter. The key must match exactly what you'll set in the |
| 86 | +script in Step 3. |
| 87 | +--- |
| 88 | + |
| 89 | +## Step 3 — Schedule posts via Buffer |
| 90 | + |
| 91 | +The scheduling script is `buffer-keynoters.py`. |
| 92 | +Run it once per keynoter. |
| 93 | + |
| 94 | +### Configure the script |
| 95 | + |
| 96 | +Open `buffer-keynoters.py` and edit the two lines at the top: |
| 97 | + |
| 98 | +```python |
| 99 | +KEYNOTER = "Leah Wasser" # must match the key in keynoters.json exactly |
| 100 | +SCHEDULED_AT = datetime(2026, 6, 16, 10, 0, |
| 101 | + tzinfo=ZoneInfo("Europe/London")) |
| 102 | +``` |
| 103 | + |
| 104 | +Set `SCHEDULED_AT` to the date and time you want the post to go live. The |
| 105 | +timezone is `Europe/London` — adjust the year, month, day, hour, and minute |
| 106 | +as needed. |
| 107 | + |
| 108 | +### Preview before posting |
| 109 | + |
| 110 | +```bash |
| 111 | +python buffer-keynoters.py --dry-run |
| 112 | +``` |
| 113 | + |
| 114 | +This prints the first 200 characters of the post for each platform without |
| 115 | +sending anything to Buffer. Use it to sanity-check the copy and confirm the |
| 116 | +right keynoter is selected. |
| 117 | + |
| 118 | +### Run |
| 119 | + |
| 120 | +```bash |
| 121 | +python buffer-keynoters.py |
| 122 | +``` |
| 123 | + |
| 124 | +The script will: |
| 125 | + |
| 126 | +1. Connect to Buffer and fetch your channel IDs |
| 127 | +2. Fetch the image from the live URL in `keynoters.json` |
| 128 | +3. Schedule the post to every platform that has text defined |
| 129 | +4. Skip any platform not connected in your Buffer account |
| 130 | +5. Print a confirmation line with the Buffer post ID for each channel |
| 131 | + |
| 132 | +Repeat for each keynoter, updating `KEYNOTER` and `SCHEDULED_AT` each time. |
| 133 | + |
| 134 | +--- |
| 135 | + |
| 136 | +## Troubleshooting |
| 137 | + |
| 138 | +| Error | Cause | Fix | |
| 139 | +|---|---|---| |
| 140 | +| `BUFFER_API_KEY not set` | Missing `.env.local` | Create `website/.env.local` with the key | |
| 141 | +| `Image upload failed (404)` | Image not deployed yet | Merge the images PR and wait for deployment | |
| 142 | +| `not connected in Buffer — skipped` | Channel not linked in Buffer | Log in to Buffer and connect the missing channel | |
| 143 | +| `not found in keynoters.json` | Typo in `KEYNOTER` | Check the exact key spelling in `keynoters.json` | |
0 commit comments