Docs / Start
Architecture
Araldo turns "announce this" requests from your products into published posts on every platform your users read. This page is the map; the decision records hold the reasons.
The shape
your product ──HTTP (API key)──▶ araldo server ──▶ Postgres ◀── araldo worker ──▶ Bluesky, Mastodon,
a person ──browser (session)─▶ (API + dashboard) (publisher, Discord, Telegram,
webhooks, tasks) sandbox …
│
└──signed webhooks──▶ your product
- One binary, two roles (ADR 0002):
araldo serverserves the API (/v1) and the dashboard;araldo workerpublishes, delivers webhooks and runs housekeeping.araldo allruns both in one process. - Postgres is the only required dependency. The publishing queue and
webhook queue are rows claimed with
FOR UPDATE SKIP LOCKEDand leases; periodic tasks use lease rows too, so any number of workers can run. Images are stored there too, unless S3-compatible storage is configured (ADR 0017); video needs it (ADR 0027). Stored credentials need master keys, local or in a Transit engine (ADR 0008). Without either, what needs them fails and the rest works.
The life of a post
- A product calls
POST /v1/postswith a template and data (or finished text), any images it uploaded to/v1/media, and a time:now,next_slot, or a timestamp. Anext_slotpost that needs approval takes its slot when approved; posts can be moved or swapped until they start publishing (ADR 0022). corerenders the text for every channel with that platform's rules (ADR 0010), refuses it with every problem listed if anything does not fit, and otherwise stores the post and one target per channel, with the text frozen. An eventpost.createdis written in the same transaction (ADR 0012).- If the brand requires approval, targets wait (
held) until an admin approves. - The worker claims due targets, one per channel at a time, records the
attempt, and calls the platform adapter
(ADR 0009). The outcome decides what
happens next (ADR 0011): published, retried
later, failed, or
needs_attentionwhen the platform may or may not have posted it and retrying could post it twice. - Each outcome is an event; webhook endpoints that subscribe get a signed delivery, retried for up to three days.
Words
| Term | Meaning |
|---|---|
| Org | A tenant: members, brands, keys. Nothing crosses orgs. |
| Brand | A product or voice in an org, with its own channels, templates, slots, approval policy and the sites whose links get UTM parameters (ADR 0016). |
| Channel | A connected account on a platform, under a brand. Test mode has sandbox channels only. |
| Template | Versioned text with a JSON Schema for its data and per-platform bodies. |
| Post | Something to publish, to one or more channels. |
| Media | An image or video a post attaches; checked against each channel's platform. |
| Engagement | A published target's likes, reposts, replies and quotes, read on a schedule (ADR 0018). |
| Target | One channel's copy of a post: the unit of publishing work. |
| Event | A record of something that happened, kept 30 days, delivered to webhooks. |
| Mode | Test or live. Decided by the API key (or the dashboard switch). |
| Ad account | An account on an ad network whose spend and results are read, under a brand (ADR 0023). |
| Analytics source | A site's web analytics (Plausible, GA4) whose signups are read, under a brand (ADR 0025). |
| Issue | A newsletter, handed to a brand's mail accounts' providers to send (ADR 0024). |
| Report | A brand's month beside the one before, across everything above (ADR 0026). |
| Operator | Whoever runs the server: araldo admin and the operator API, outside any org (ADR 0028, ADR 0031). |
Code layout
See ADR 0002. In short: internal/core
holds every use case and is the only thing the API (internal/api), the
dashboard (internal/web) and the CLI (internal/cli) call;
internal/store holds all SQL; platform adapters live in
internal/platform/<name>.
Security in one screen
- Tenancy is enforced in
core(every query scoped to the caller's org) and in the schema (composite foreign keys) (ADR 0004). - Stored secrets are envelope-encrypted per org and bound to their row (ADR 0008); API keys, sessions and recovery codes are stored as hashes.
- Sign-in uses argon2id passwords, TOTP with recovery codes, sessions with CSRF tokens, and re-authentication for sensitive actions (ADR 0007).
- Outbound requests to addresses tenants choose (webhooks, Mastodon servers, custom Bluesky PDSs) refuse private networks unless the operator allows them.