Configuration
postline reads postline.config.yaml from the project root. Every key except
brand.name has a default.
brand:
name: Acme # required
url: acme.com # shown bottom-right on cards
logo: assets/logo.svg # SVG is inlined; PNG/JPG/WebP embedded
showName: true # print brand.name next to the logo
font:
family: Inter
url: https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800&display=block
# or a local file, embedded into the card:
# file: assets/Inter.woff2
colors:
background: "#0f1115"
panel: "#161a21" # row and panel fill
border: "#2a303b"
text: "#eef1f5"
muted: "#98a1ae" # notes, labels, footer
accent: "#7cc4ff" # eyebrow, second headline line, numbers, url
ok: ["#1f6f4a", "#7fd3a5"] # chip [fill, text]
warn: ["#8a5a10", "#f0c46a"]
bad: ["#a83a2c", "#f3907f"]
chips: # chip text -> tone. Anything else is neutral.
allowed: ok
detected: warn
blocked: bad
timezone: UTC # IANA name. All `at:` values are in this zone.
schedule:
weekStartsOn: mon # what "a week" means for plan/status/brief
days: [mon, tue, wed, thu, fri]
times: ["09:00"] # 24h
postsDir: posts
outDir: out # rendered cards
stateDir: .postline # commit ledger.json; cards.json is a local render cache
card:
size: square # square 1200x1200, landscape 1200x627, portrait 1080x1350
format: png # or jpeg
quality: 90 # jpeg only
scale: 2 # device pixel ratio; 2 gives crisp text
allowedHosts: [] # extra hosts cards may load from; see design-systems.md
publish:
lateMinutes: 180 # skip posts this far past their slot
maxAttempts: 3 # stop retrying a failing channel after this
requestTimeoutMs: 30000 # bound every external API request
design: # optional; see design-systems.md
tokens: design/tokens.json # DTCG or Style Dictionary JSON; reference as "{color.brand}"
stylesheet: design/brand.css # extra CSS applied to every card
templates: design/templates # <name>.html -> card.layout: <name>
library: # see graphics-library.md
enabled: true # default true, false in CI
dir: ~/Pictures/postline/<brand>
scale: 3 # full-quality copies at 3x
channels: {} # see channels.md
notify: # optional; see health-and-alerts.md
format: slack # slack, discord, ntfy or json
urlEnv: POSTLINE_NOTIFY_URL # env var holding the webhook or topic URL (or set url:)
on: [failed, uncertain, blocked, doctor]
rules:
maxHashtags: 3
allowEmoji: true
allowedLinkDomains: [] # empty means any domain
hookMaxChars: 140 # first line, warning only
requireImage: false # every ready post needs a card or image
requireAlt: false # missing alt text on an image post is an error, not a warning
topicCooldownDays: 14 # warn when a topic repeats inside this window
bannedPhrases: []
Design tokens in brand settings
With design.tokens set, any value under brand.colors (including each half of the
chip pairs) and brand.font.family can be a token reference such as
"{color.brand.blue}". Unknown tokens stop with an error naming the token.
See design-systems.md.
Fonts and offline rendering
With font.url, Chromium fetches the font while rendering, so the machine needs
network access to that host. For fully offline or locked-down CI, download the
font and use font.file; it is embedded into the card HTML.
Environment variables
Secrets never go in the config file. Each channel reads its token from an
environment variable (see channels.md). POSTLINE_CHROMIUM points
rendering at a specific Chromium binary. POSTLINE_NOTIFY_URL holds the alert
destination and POSTLINE_NOTIFY_SECRET signs json alerts (both renamable with
notify.urlEnv and notify.secretEnv; see health checks and alerts). NO_COLOR
turns off coloured output.
Configuration is validated before any command runs. Invalid dates, unknown status values, unsafe post IDs, malformed URLs and out-of-range numeric settings fail closed rather than being coerced into a publishable value.