Automates drafting and publishing articles to Substack and Medium with Apify and Notion integration
Overall
score
18%
Does it follow best practices?
If you maintain this skill, you can automatically optimize it using the tessl CLI to improve its score:
npx tessl skill review --optimize ./path/to/skillValidation for skill structure
Automates drafting and publishing articles to Substack and Medium with workflow automation.
{
"mcpServers": {
"apify": {
"command": "npx",
"args": ["-y", "@apify/mcp-server"],
"env": { "APIFY_API_TOKEN": "${APIFY_API_TOKEN}" }
},
"notion": {
"command": "npx",
"args": ["-y", "@makenotion/mcp-server"],
"env": { "NOTION_API_KEY": "${NOTION_API_KEY}" }
},
"slack": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-slack"],
"env": { "SLACK_BOT_TOKEN": "${SLACK_BOT_TOKEN}" }
}
}
}| Tool | Capabilities |
|---|---|
Bash(apify:*) | Execute browser automation for publishing |
MCP(apify:*) | Run web scraping/automation actors |
MCP(notion:*) | Store editorial calendar, track drafts |
MCP(slack:*) | Send approval notifications |
Apify Token
export APIFY_API_TOKEN="your-token"Notion Integration
Slack (optional)
export SLACK_BOT_TOKEN="xoxb-your-token"// 1. Analyze request
const topic = "The impact of Agentic AI on coding";
const audience = "developers";
const tone = "professional";
// 2. Research if needed
const context = await apify.actor("apify/firecrawl-scraper", {
urls: [`https://news.ycombinator.com/?q=${topic}`]
});
// 3. Generate content
const draft = await generateArticle({ topic, audience, tone, context });
// 4. Save to drafts folder
const filename = `memory/drafts/${dateSlug(topic)}.md`;
await fs.write(filename, draft);
// 5. Log to Notion editorial calendar
await notion.createPage("Editorial Calendar", {
title: draft.title,
status: "Draft",
scheduledDate: null,
platform: "both"
});// 1. Read draft
const content = await fs.read("memory/drafts/agentic-ai.md");
// 2. Navigate to platform
await browser.goto("https://medium.com/new-post");
// 3. Fill content
await browser.fill(".title-input", content.title);
await browser.fill(".body-editor", content.body);
// 4. Add tags
for (const tag of content.tags) {
await browser.click(".tag-input");
await browser.type(tag);
}
// 5. Publish or save draft
if (mode === "live") {
await browser.click(".publish-button");
await slack.notify("#content", `Published: ${content.title}`);
} else {
await browser.click(".save-draft");
}// 1. Read content
const content = await fs.read(draftPath);
// 2. Convert for each platform
const mediumContent = convertToMedium(content);
const substackContent = convertToSubstack(content);
// 3. Publish to Medium
await publishToMedium(mediumContent);
// 4. Publish to Substack
await publishToSubstack(substackContent);
// 5. Update Notion
await notion.updatePage(draftId, { status: "Published" });| Command | Description |
|---|---|
draft "topic" | Generate article draft |
publish <file> <platform> | Publish to platform |
publish <file> <platform> live | Publish live |
schedule <file> <platform> <datetime> | Schedule post |
| Error Code | Meaning | Fix |
|---|---|---|
AUTH_001 | Not logged in | Check credentials, re-login |
PUBLISH_001 | Platform changed UI | Update selectors |
RATE_001 | Rate limited | Wait and retry |
VALIDATE_001 | Content validation failed | Check format |
async function publishWithDryRun(content, platform, dryRun = true) {
// Validate content
if (!content.title || !content.body) {
throw new Error("Missing required fields");
}
// Preview
console.log("=== PREVIEW ===");
console.log(`Title: ${content.title}`);
console.log(`Platform: ${platform}`);
if (dryRun) {
console.log("DRY RUN - No actual publishing");
return { status: "preview" };
}
// Actually publish
return await platform.publish(content);
}Skill v2.0 - Content Publisher with MCP
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.