🔧 AI Nachrichten ChatGPT showing blank screen [Fix](05.09.2026 um 19:55 Uhr)
⚠️ Malware / Trojaner / VirenSofort deinstallieren: Diese 19 Browser-Erweiterungen sind mit Malware verseucht(06.09.2026 um 08:00 Uhr)
🔧 AI Nachrichten ChatGPT showing blank screen [Fix](05.09.2026 um 19:55 Uhr)
⚠️ Malware / Trojaner / VirenSofort deinstallieren: Diese 19 Browser-Erweiterungen sind mit Malware verseucht(06.09.2026 um 08:00 Uhr)

🔧 Programmierung 🕛 kürzlich 9 Min Lesezeit
0

How we run our newsletter on our own Astro stack instead of Substack

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

This is a post about plumbing. We send a newsletter, it goes out from our own site, and over the last few months that setup turned into something I actually like working on. Here is the whole thing.






Why we left Substack



The newsletter used to live on Substack. We moved it for one reason: SEO control.



We were in the middle of fixing indexation on getbeton.ai, working through every factor that could be holding pages back. Substack is a black box for yours truly. You don't control the sitemap and canonical tags, you can't set a real lastmod, and the URL lives on a domain that isn't yours.



When you're trying to eliminate variables, a platform you can't configure is usually my first choice.



So the newsletter and the blog became the same thing, on our own domain, where we own every header and every tag.








The reading UI we actually wanted



This is the part that made it fun. Once you own the renderer (and have Claude at your side), you can ship small things quickly



The one I use most is sidenotes.[1] On a wide screen, a footnote floats out into the left margin next to the line it belongs to, the way a good print book does it. On mobile it collapses to a tappable reference.



They're plain HTML in the markdown, styled once in our prose stylesheet, and they double as quiet contextual CTAs where they fit.



We also render a TL;DR block at the top and an FAQ at the bottom of every post, both pulled from the same frontmatter that feeds the structured data.



Owning the renderer pays off in small typographic things too. Monospace text renders the way we want it to, not the way a platform theme decided.




CODE
Code snippets look however we choose, with the syntax styling we pick, instead of whatever a generic editor pastes in. 






Imo for a blog that talks about tooling, that matters more than it sounds.



It also makes the structured data trivial. The TL;DR and FAQ already live in frontmatter, so embedding them in JSON-LD is a few lines at render time, not a plugin and not hand-written markup. Cleaner structured data, written once, and the same source feeds both the page and the rich result.



None of this is baseline. A platform gives you a title, a body, and a subscribe box. Owning the stack means the reading experience is yours to shape, and that is reason enough to build a small thing now and then.






Sending: Resend



Sending runs on Resend.



The audience is a Resend audience. New subscribers come in through a form on the site that posts to a small API route and adds them to that audience. The sending domain is verified on getbeton.ai with SPF, DKIM, and DMARC, so mail authenticates as us and lands in the inbox instead of spam.



Each campaign is a small Node script. It builds the email HTML per recipient, pulls the subscribed contacts, and sends with a delay between messages to stay polite. Every send carries a List-Unsubscribe header and an unsubscribe link, and the script skips anyone already unsubscribed.






Analytics: PostHog, on its own dashboard



We didn't want to lose open and click tracking by leaving a platform, so we rebuilt it with -– surprise-surprise -- with Posthog.



Two endpoints do the work. A 1x1 tracking pixel records opens. A click redirector wraps every link, records the click, then 302s the reader to the real destination with UTM tags attached.



Both fire PostHog events, newsletter_opened and newsletter_link_clicked, tagged with the campaign and the recipient. They're small enough to read in full.



The open pixel is an Astro endpoint that returns a 1x1 gif and captures an event on the way out. The PostHog call is fire-and-forget, so a slow capture never blocks the image:




CODE
// src/pages/api/track/pixel.png.ts
const PIXEL = Buffer.from('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7', 'base64'); // 1x1 gif

export const GET: APIRoute = async ({ url }) => {
const campaign = url.searchParams.get('c') || 'unknown';
const email = url.searchParams.get('e') || 'anonymous';

await fetch(`${POSTHOG_HOST}/capture/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
api_key: POSTHOG_KEY,
event: 'newsletter_opened',
distinct_id: email,
properties: { campaign, source: 'email' },
}),
}).catch(() => {});

return new Response(PIXEL, {
headers: { 'Content-Type': 'image/gif', 'Cache-Control': 'no-store' },
});
};






The click redirector does the same, plus one thing that matters: it only redirects to hosts we trust. A naive redirector that forwards anywhere is a phishing tool with your domain on it.




CODE
// src/pages/api/track/click.ts
const ALLOWED_HOSTS = ['getbeton.ai', 'www.getbeton.ai', 'inspector.getbeton.ai', 'github.com'];

export const GET: APIRoute = async ({ url }) => {
const target = url.searchParams.get('u') || 'https://www.getbeton.ai';
const campaign = url.searchParams.get('c') || 'unknown';
const label = url.searchParams.get('l') || 'unknown';
const email = url.searchParams.get('e') || 'anonymous';

const parsed = new URL(target);
if (!ALLOWED_HOSTS.some(h => parsed.hostname === h || parsed.hostname.endsWith(`.${h}`))) {
return Response.redirect('https://www.getbeton.ai', 302); // refuse untrusted targets
}

await fetch(`${POSTHOG_HOST}/capture/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
api_key: POSTHOG_KEY,
event: 'newsletter_link_clicked',
distinct_id: email,
properties: { campaign, link_label: label, target_url: target },
}),
}).catch(() => {});

return Response.redirect(target, 302);
};






That's the whole tracking layer. Two endpoints, no SDK in the email, and the data lands in the same PostHog project as everything else.



All of it rolls up to a dedicated PostHog dashboard, separate from product analytics, so newsletter performance reads cleanly without digging through everything else.



. The tracking endpoints, the sidenote styles, the sitemap-lastmod trick, all of it. Clone it and you have the same technical implementation we run, no platform required.






The actual point



Doing a blog gets more fun when you can build the non-baseline parts yourself. Sidenotes didn't move a metric. They made the posts nicer to read, they took an afternoon, and they only exist because the newsletter lives on our own stack instead of someone else's. That trade, a bit more plumbing for full control, has paid for itself in SEO and in the small things we get to build along the way.



Spread the word and share this one with your marketing friend.



— Vlad






Originally posted on the Beton blog: https://www.getbeton.ai/blog/how-we-do-newsletter-at-beton/

Vollständiger Original-Bericht
Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 43%
🟡 In Evaluierung 21%
🟢 Keine Auswirkung 13%
Spannende Innovation 23%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
2 Quellen
Jetzt patchen! Angreifer attackieren JFrog Artifactory und machen sich zu Admins
1 Quelle
OpenAI’s new Astra model is finally here – why safety experts are worried
1 Quelle
WhatsApp-Schwachstelle: Zugriff auf Fotos bei gesperrtem Android-Handy
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten How we run our newsletter on our own Astro stack instead of Substack

Thematisch verwandte Begriffe: newsletter, Astro, stack, instead · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...