Skip to content

Commit 3dda082

Browse files
committed
feat(marketing): add blog-to-X distribution pipeline
Add a lightweight workflow for generating X/Twitter post variants from blog posts with UTM tracking, hashtag generation, and thread templates. Closes #4
1 parent e6d79cc commit 3dda082

4 files changed

Lines changed: 357 additions & 1 deletion

File tree

Makefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: build serve dev clean
1+
.PHONY: build serve dev clean distribute
22

33
build:
44
hype blog build
@@ -17,3 +17,6 @@ docker-build:
1717

1818
docker-run:
1919
docker run -p 3000:3000 hypemd-dev
20+
21+
distribute:
22+
@./marketing/distribute.sh $(SLUG)

marketing/distribute.sh

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
SITE_URL="https://hypemd.dev"
5+
HANDLE="@hype_markdown"
6+
MAX_CHARS=280
7+
8+
usage() {
9+
echo "Usage: $0 <content-slug>"
10+
echo ""
11+
echo "Generate X/Twitter post variants for a blog post."
12+
echo ""
13+
echo "Examples:"
14+
echo " $0 getting-started"
15+
echo " $0 ai-authoring-workflow"
16+
exit 1
17+
}
18+
19+
if [[ $# -lt 1 ]]; then
20+
usage
21+
fi
22+
23+
SLUG="$1"
24+
FILE="content/${SLUG}/module.md"
25+
26+
if [[ ! -f "$FILE" ]]; then
27+
echo "Error: $FILE not found" >&2
28+
exit 1
29+
fi
30+
31+
title=$(grep -m1 '^# ' "$FILE" | sed 's/^# //')
32+
details=$(awk '/<details>/{found=1; next} /<\/details>/{if(found) exit} found{print}' "$FILE")
33+
slug=$(echo "$details" | grep '^slug:' | head -1 | sed 's/^slug: *//')
34+
seo_desc=$(echo "$details" | grep '^seo_description:' | head -1 | sed 's/^seo_description: *//')
35+
tags=$(echo "$details" | grep '^tags:' | head -1 | sed 's/^tags: *//')
36+
author=$(echo "$details" | grep '^author:' | head -1 | sed 's/^author: *//')
37+
38+
if [[ -z "$title" ]]; then
39+
echo "Error: could not parse title from $FILE" >&2
40+
exit 1
41+
fi
42+
43+
if [[ "$slug" == docs/* || "$slug" == "docs" ]]; then
44+
echo "WARNING: This appears to be a documentation page (slug: ${slug}). Docs pages are typically not promoted on social media." >&2
45+
echo "" >&2
46+
fi
47+
48+
is_tutorial=false
49+
if echo "$tags" | grep -qi 'tutorial'; then
50+
is_tutorial=true
51+
fi
52+
53+
build_url() {
54+
local variant="$1"
55+
echo "${SITE_URL}/${slug}/?utm_source=twitter&utm_medium=social&utm_content=${variant}"
56+
}
57+
58+
print_variant() {
59+
local label="$1"
60+
local text="$2"
61+
local chars=${#text}
62+
echo "=== ${label} ==="
63+
echo "$text"
64+
echo ""
65+
echo "Characters: ${chars}"
66+
if [[ $chars -gt $MAX_CHARS ]]; then
67+
echo "WARNING: Exceeds ${MAX_CHARS} character limit by $((chars - MAX_CHARS)) characters"
68+
fi
69+
echo ""
70+
}
71+
72+
build_hashtags() {
73+
local -a all=("#HypeMarkdown" "#Golang" "#OpenSource")
74+
75+
IFS=',' read -ra tag_arr <<< "$tags"
76+
for tag in "${tag_arr[@]}"; do
77+
tag=$(echo "$tag" | sed 's/^ *//;s/ *$//')
78+
case "$tag" in
79+
tutorial|getting-started|hype) ;;
80+
docker) all+=("#Docker") ;;
81+
ai|claude) all+=("#AI") ;;
82+
workflow) all+=("#DevWorkflow") ;;
83+
authoring) all+=("#TechWriting") ;;
84+
training) all+=("#Training") ;;
85+
documentation|docs) all+=("#Documentation") ;;
86+
release*) all+=("#ReleaseNotes") ;;
87+
handbook) all+=("#EngineeringHandbook") ;;
88+
*) all+=("#${tag^}") ;;
89+
esac
90+
done
91+
92+
local seen=""
93+
local result=""
94+
for h in "${all[@]}"; do
95+
if [[ "$seen" != *"$h"* ]]; then
96+
result="$result $h"
97+
seen="$seen $h"
98+
fi
99+
done
100+
echo "${result# }"
101+
}
102+
103+
url_technical=$(build_url "technical")
104+
url_founder=$(build_url "founder")
105+
url_hook=$(build_url "hook")
106+
107+
if [[ "$is_tutorial" == true ]]; then
108+
technical="${seo_desc} ${url_technical}"
109+
founder="We built Hype because documentation shouldn't lie. Here's how to get started with dynamic Markdown that validates everything at build time: ${url_founder}"
110+
hook="Your Markdown can run code now. ${url_hook}"
111+
112+
case "$slug" in
113+
getting-started)
114+
technical="Learn how to install Hype and create your first dynamic Markdown document with build-time code execution. ${url_technical}"
115+
founder="We built Hype because documentation shouldn't lie. Here's a quick guide to get started: ${url_founder}"
116+
hook="What if your Markdown could execute code and catch errors before publish? ${url_hook}"
117+
;;
118+
deploying-with-docker)
119+
technical="Deploy a Hype-powered blog with Docker — from Dockerfile to production with auto-rebuilds. ${url_technical}"
120+
founder="We wanted deploying a Hype blog to be as simple as 'docker build && docker run'. Here's how: ${url_founder}"
121+
hook="Ship your Hype blog in a container. ${url_hook}"
122+
;;
123+
*)
124+
technical="${seo_desc} ${url_technical}"
125+
founder="We built this with Hype because ${title,,} shouldn't be harder than it needs to be: ${url_founder}"
126+
hook="${title} — powered by dynamic Markdown. ${url_hook}"
127+
;;
128+
esac
129+
else
130+
technical="${seo_desc} ${url_technical}"
131+
founder="We've been using Hype for ${title,,} and it's been a game changer. Here's how: ${url_founder}"
132+
hook="${title} — see how teams are using Hype. ${url_hook}"
133+
fi
134+
135+
echo "========================================"
136+
echo "X/Twitter Posts for: ${title}"
137+
echo "Post type: $(if $is_tutorial; then echo 'Tutorial'; else echo 'Usage Scenario'; fi)"
138+
echo "========================================"
139+
echo ""
140+
141+
print_variant "TECHNICAL VARIANT" "$technical"
142+
print_variant "FOUNDER VOICE VARIANT" "$founder"
143+
print_variant "SHORT HOOK VARIANT" "$hook"
144+
145+
hashtags=$(build_hashtags)
146+
echo "=== HASHTAGS ==="
147+
echo "$hashtags"
148+
echo ""
149+
150+
if [[ "$is_tutorial" == true ]]; then
151+
url_thread=$(build_url "thread")
152+
echo "=== THREAD VARIANT (3 posts) ==="
153+
echo ""
154+
echo "1/3:"
155+
echo "${seo_desc}"
156+
echo ""
157+
echo "A thread on ${title,,} with @hype_markdown 🧵"
158+
echo ""
159+
echo "2/3:"
160+
first_section=$(sed -n '/^## /{s/^## //;p;q;}' "$FILE")
161+
if [[ -n "$first_section" ]]; then
162+
echo "It starts with ${first_section,,} — Hype makes this straightforward because your Markdown is dynamic. Code blocks execute, files get included, and everything is validated at build time."
163+
else
164+
echo "Hype makes this straightforward because your Markdown is dynamic. Code blocks execute, files get included, and everything is validated at build time."
165+
fi
166+
echo ""
167+
echo "3/3:"
168+
echo "Full walkthrough here: ${url_thread}"
169+
echo ""
170+
echo "${hashtags}"
171+
echo ""
172+
fi
173+
174+
echo "=== UTM URLS ==="
175+
echo "Technical: ${url_technical}"
176+
echo "Founder: ${url_founder}"
177+
echo "Hook: ${url_hook}"
178+
if [[ "$is_tutorial" == true ]]; then
179+
echo "Thread: $(build_url "thread")"
180+
fi

marketing/distribution-workflow.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# Distribution Workflow
2+
3+
## Quick Start
4+
5+
```bash
6+
./marketing/distribute.sh <slug>
7+
# or
8+
make distribute SLUG=<slug>
9+
```
10+
11+
Example:
12+
```bash
13+
make distribute SLUG=getting-started
14+
```
15+
16+
## Process
17+
18+
1. **Publish** the blog post (merge to main, auto-deploys via Dokploy)
19+
2. **Generate** post variants: `make distribute SLUG=<slug>`
20+
3. **Review and edit** the generated variants — tweak tone, add context
21+
4. **Post** to X (manually, via X scheduler, or via Buffer)
22+
5. **Cross-post** to LinkedIn if appropriate (rewrite for longer format)
23+
24+
## UTM Conventions
25+
26+
All generated URLs include UTM parameters for tracking.
27+
28+
| Parameter | Value | Purpose |
29+
|-----------|-------|---------|
30+
| `utm_source` | `twitter`, `linkedin`, `newsletter` | Where the click came from |
31+
| `utm_medium` | `social`, `email` | Channel type |
32+
| `utm_content` | `technical`, `founder`, `hook`, `thread` | Which variant was clicked |
33+
34+
Example URL:
35+
```
36+
https://hypemd.dev/getting-started/?utm_source=twitter&utm_medium=social&utm_content=technical
37+
```
38+
39+
## Which Posts to Promote
40+
41+
| Post Type | Promote? | Example |
42+
|-----------|----------|---------|
43+
| Tutorial posts | Yes | getting-started, deploying-with-docker |
44+
| Usage scenario posts | Yes | ai-authoring-workflow, release-notes-pipeline |
45+
| Documentation posts (docs-*) | No | These are reference material |
46+
47+
## Post Timing Guidelines
48+
49+
| Content Type | Best Time | Best Days |
50+
|-------------|-----------|-----------|
51+
| Tutorials | 9-11am ET | Weekdays |
52+
| Usage scenarios | 10am-1pm ET | Tue-Thu |
53+
| Announcements | 9am-12pm ET | Any weekday |
54+
55+
## Scheduler Integration
56+
57+
### X Built-in Scheduler
58+
59+
1. Compose your tweet on X
60+
2. Click the calendar icon
61+
3. Pick date and time
62+
4. Schedule
63+
64+
Best for one-off posts.
65+
66+
### Buffer (Free Tier)
67+
68+
- 3 channels, 10 scheduled posts per channel
69+
- Paste generated text, set schedule
70+
- Best for batching a week of posts
71+
72+
### X API v2 (Advanced)
73+
74+
For automated posting, use the X API directly:
75+
76+
```bash
77+
curl -X POST "https://api.x.com/2/tweets" \
78+
-H "Authorization: Bearer $X_BEARER_TOKEN" \
79+
-H "Content-Type: application/json" \
80+
-d '{"text": "Your tweet text here"}'
81+
```
82+
83+
Store the bearer token in an environment variable. Never commit tokens to the repo.
84+
85+
## Measuring Results
86+
87+
- Check UTM parameters in your analytics tool
88+
- `utm_content` tells you which variant performed best
89+
- Compare `technical` vs `founder` vs `hook` click-through rates
90+
- Iterate on templates based on what resonates

marketing/templates.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# X/Twitter Post Templates for hypemd.dev
2+
3+
## Variables
4+
5+
| Variable | Source | Example |
6+
|----------|--------|---------|
7+
| `{title}` | First `# ` heading | Getting Started with Hype |
8+
| `{slug}` | Frontmatter | getting-started |
9+
| `{seo_description}` | Frontmatter | Learn how to install Hype... |
10+
| `{tags}` | Frontmatter | tutorial, getting-started, hype |
11+
| `{author}` | Frontmatter | Gopher Guides |
12+
| `{url}` | Generated with UTM | https://hypemd.dev/getting-started/?utm_source=twitter&... |
13+
14+
## Single Post Templates
15+
16+
### Tutorial Posts
17+
18+
**Technical:**
19+
> {seo_description} {url}
20+
21+
**Founder Voice:**
22+
> We built this with Hype because {title} shouldn't be harder than it needs to be: {url}
23+
24+
**Short Hook:**
25+
> {title} — powered by dynamic Markdown. {url}
26+
27+
### Usage Scenario Posts
28+
29+
**Technical:**
30+
> {seo_description} {url}
31+
32+
**Founder Voice:**
33+
> We've been using Hype for {title} and it's been a game changer. Here's how: {url}
34+
35+
**Short Hook:**
36+
> {title} — see how teams are using Hype. {url}
37+
38+
## Thread Templates
39+
40+
### Tutorial Thread (3 posts)
41+
42+
**Post 1 (Hook):**
43+
> {seo_description}
44+
>
45+
> A thread on {title} with @hype_markdown
46+
47+
**Post 2 (Key insight):**
48+
> It starts with {first_section} — Hype makes this straightforward because your Markdown is dynamic. Code blocks execute, files get included, and everything is validated at build time.
49+
50+
**Post 3 (CTA):**
51+
> Full walkthrough here: {url}
52+
>
53+
> {hashtags}
54+
55+
### Announcement Thread (4 posts)
56+
57+
**Post 1:** Bold claim about the problem being solved.
58+
59+
**Post 2:** Why existing solutions fall short.
60+
61+
**Post 3:** How Hype solves it differently (with a concrete example).
62+
63+
**Post 4:** Link + CTA + hashtags.
64+
65+
## Hashtag Reference
66+
67+
**Always include:**
68+
- `#HypeMarkdown`
69+
- `#Golang`
70+
- `#OpenSource`
71+
72+
**Conditional (based on tags):**
73+
74+
| Tag | Hashtag |
75+
|-----|---------|
76+
| docker | #Docker |
77+
| ai, claude | #AI |
78+
| workflow | #DevWorkflow |
79+
| authoring | #TechWriting |
80+
| training | #Training |
81+
| documentation, docs | #Documentation |
82+
| release* | #ReleaseNotes |
83+
| handbook | #EngineeringHandbook |

0 commit comments

Comments
 (0)