A new blog post goes live, and the immediate imperative is to inform the subscriber base. For this particular operation, the chosen mechanism involves Resend, with subscribers managed within a Resend Segment. Broadcasting to them means a call to the Resend Broadcasts API. The core architectural question then becomes: how does one orchestrate this broadcast as an integral part of the publishing workflow, without bogging down the build pipeline?
The elegant answer, as implemented here, is a standalone Node.js script—scripts/notify-new-post.js. This script is designed to be run manually, post-publication. It intelligently reads frontmatter directly from the content files, constructs an HTML email, offers a terminal-based preview, and crucially, demands explicit confirmation before dispatching the notification to every subscriber. This is a deliberate design choice, prioritizing control and minimizing unintended triggers.
Why Not a Build Hook?
The decision to opt for a manual script over a build hook is grounded in pragmatic operational concerns. A build hook, by its nature, would execute on every single deployment. This includes routine updates like CSS adjustments or minor draft revisions—changes that absolutely do not warrant a subscriber notification. To filter these out within a build process would necessitate complex logic to discern genuine content updates from benign modifications, a complexity that can quickly spiral.
Running the script manually introduces intentional friction. This is not a bug; it’s a feature. It mandates a moment of human review before a communication is sent to the entire subscriber list. This deliberate pause ensures that only validated, new content triggers outreach, establishing a more strong and less intrusive communication channel. It’s the sensible default.
The Tech Stack: Minimalism as a Virtue
The script itself is remarkably lean, relying on a single external dependency: @inquirer/prompts, which provides the interactive select and confirmation prompts necessary for user interaction. Beyond that, it exclusively utilizes Node.js built-in modules—readFileSync, readdirSync, and various path utilities. The absence of framework-specific tooling like Astro or data validation libraries such as Zod, and even content collection abstractions, underscores a commitment to minimal overhead for this specific task.
To parse the necessary frontmatter, the script employs a custom, minimal parser rather than integrating a full-fledged YAML library. This function is designed to handle only the essential key: value pairs and the >- block scalar for descriptions.
function parseFrontmatter(content) {
const match = content.match(/^---\r?\n([\s\S]*?)\n---/);
if (!match) return {};
const yaml = match[1];
const result = {};
for (const line of yaml.split(/\r?\n/)) {
const m = line.match(/^(\w+): \s*["'>]?(.*?)["']?\s*$/);
if (m) result[m[1]] = m[2].trim();
}
// Handle YAML block scalar for description (>- or >)
const descBlock = yaml.match(/^description:\s*>-?\r?\n((?:[ \t]+.+\r?\n?)*)/m);
if (descBlock) {
result.description = descBlock[1]
.split(/\r?\n/)
.map((l) => l.trim())
.filter(Boolean)
.join(" ");
}
return result;
}
This approach is pragmatic; the script doesn’t require a deep understanding of the YAML Abstract Syntax Tree (AST), only the specific fields relevant to notification content and timing.
The listPostIds function is responsible for identifying posts published within the preceding week that are not marked as drafts. Robustness is built in: each directory read is encased in a try/catch block. This ensures that unreadable or malformed posts are silently bypassed, preventing script failure from isolated issues.
function listPostIds() {
const postsDir = join(root, "collections", "posts");
const oneWeekAgo = Date.now() - 7 * 24 * 60 * 60 * 1000;
return readdirSync(postsDir, { withFileTypes: true })
.filter((d) => d.isDirectory())
.map((d) => {
try {
const content = readFileSync(join(postsDir, d.name, "index.md"), "utf8");
const fm = parseFrontmatter(content);
const pubDate = fm.pubDate ? new Date(fm.pubDate).getTime() : 0;
const isDraft = fm.draft === "true";
return { id: d.name, pubDate, isDraft };
} catch {\nreturn null;\n}
})
.filter((p) => p && !p.isDraft && p.pubDate >= oneWeekAgo)
.sort((a, b) => b.pubDate - a.pubDate)
.map((p) => p.id);
}
Configuration and Security
Secrets are managed through a .env file located at the project root. Node 20.12’s process.loadEnvFile handles this, with shell environment variables taking precedence for security and flexibility. The required environment variables are:
RESEND_API_KEY: An API key from Resend with broadcast sending permissions.RESEND_SEGMENT_ID: The specific Resend Segment ID to target for broadcasts.SITE_URL: The base URL of the site, used to construct links to new posts (defaults tohttps://sourcier.uk).
Optional, but recommended, variables include:
NOTIFY_FROM_EMAIL: Defines theFrom:address in the broadcast email, defaulting toRoger @ Sourcier <[email protected]>.RESEND_TOPIC_ID: If supplied, attaches a Resend topic to the broadcast, offering enhanced unsubscribe granularity for users.
Crafting the Email
The broadcast is designed to be inclusive, providing both an HTML body for rich email clients and a plain-text fallback for those that cannot render HTML. This dual approach ensures maximum reach and readability across diverse email environments.
Creating the HTML email involves constructing an inline-styled string. This method is necessitated by the limitations of many email clients, which have poor support for external stylesheets or CSS custom properties. Every style must be applied inline, utilizing safe font stacks for broad compatibility.
``javascript
function buildHtml() {
return
Hi — I just published something new on Sourcier.
${title}
${excerpt ? `${excerpt}
` This implementation of sending new post notifications via Resend is a proof to the power of well-defined, single-purpose scripts. By sidestepping the complexities of build pipeline integration and embracing a manual, review-oriented workflow, it achieves operational simplicity and reliability. The choice to use minimal dependencies further reinforces its maintainability. It’s a practical application of the “just enough” principle in software development for common publishing tasks. Looking ahead, one could envision further automation, perhaps a scheduled cron job if the manual trigger becomes too burdensome. However, the current implementation’s deliberate friction is a powerful safeguard against accidental mass emails—a scenario few publishers would welcome. The data points it relies on are straightforward: publication date, draft status, and post URL, all derived from frontmatter. The market for such tools is crowded, but Resend’s specific capabilities, combined with this script's focused utility, carve out a distinct niche. The key takeaway for developers facing similar notification needs is the value of isolation: keeping distinct tasks separate from the core build process often leads to more resilient and manageable systems. It’s a lesson in architectural discipline that bears repeating. --- ### 🧬 Related Insights - **Read more:** [DIY AI Controller: $15 Bulb Meets MIDI Pad](https://devtoolsfeed.com/article/16-botoes-1-lampada-wifi-e-um-agente-de-ai-projeto-de-sexta-feira/) - **Read more:** [Beyond Rocks: The Backyard Quarry Reveals a Universal System Pattern](https://devtoolsfeed.com/article/the-backyard-quarry-part-8-from-rocks-to-reality/)