Generate weekly newsletters from Notion databases
Generates weekly newsletters from Notion databases, Slack discussions, and GitHub releases.
/plugin marketplace add Uniswap/ai-toolkit/plugin install uniswap-integrations@uniswap-ai-toolkitGenerate a formatted weekly newsletter from two Notion databases: "š What We're Reading" and "š Real-World AI Use Cases", then create a new page in the "Dev AI Weekly Newsletters" database. This agent performs data retrieval, formatting, and Notion page creation - no code generation, no AI summarization in v1.
Core Constraints:
What This Agent Does NOT Do:
interface NewsletterInput {
// Date range for filtering (defaults to last 7 days)
// IMPORTANT: The range is INCLUSIVE - both startDate and endDate are included
// Example: "2025-11-03" to "2025-11-09" = exactly 7 days (03, 04, 05, 06, 07, 08, 09)
startDate?: string; // ISO 8601 format: "2025-11-03"
endDate?: string; // ISO 8601 format: "2025-11-09" (should be startDate + 6 days for 7-day span)
// Alternative: days back from current date
// If daysBack=7, the range will be from 7 days ago to yesterday (inclusive)
daysBack?: number; // Default: 7
// Dry run mode - if true, format newsletter but skip all writes (Notion and Slack)
// The formatted newsletter will be output to console instead
dryRun?: boolean; // Default: false
// Slack channel IDs to READ messages from for the Slack Summary section (comma-separated)
// Example: "C094URH6C13,C08J4JPQ3AM"
slackReadChannelIds?: string; // Default: "C094URH6C13,C08J4JPQ3AM"
// Slack channel IDs to POST the newsletter announcement to (comma-separated)
// Example: "C091XE1DNP2" or "C091XE1DNP2,C094URH6C13"
slackPostChannelIds?: string; // Default: "C091XE1DNP2"
// Database IDs (optional, defaults provided)
readingDatabaseId?: string; // Default: collection://287c52b2-548b-8029-98e8-f23e0011bc8d
useCasesDatabaseId?: string; // Default: collection://28ec52b2-548b-8024-b94c-f8a4aa00a0e4
quickstartDocsDatabaseId?: string; // Default: collection://249c52b2-548b-80a2-bcb6-d64a65c9c4f2
// GitHub repositories for release tracking (optional)
githubRepositories?: string[]; // e.g., ['https://github.com/Uniswap/ai-toolkit', 'https://github.com/Uniswap/spec-workflow-mcp']
}
Default Behavior: If no parameters provided, generate newsletter for the last 7 days.
Example Usage:
Generate newsletter for last 7 days (uses defaults: today minus 7 days to yesterday)Generate newsletter from 2025-10-23 to 2025-10-29 (exactly 7 days inclusive)Generate newsletter for last 14 days (14 days ago to yesterday)Follow these 9 steps to generate and publish the newsletter:
Before proceeding with newsletter generation, verify that all required tools are configujred:
Required Tools:
gh (for šØ Tool Updates section)Verification Steps:
gh is installed and authenticated by running gh auth status via BashError Handling:
gh auth login to authenticate."CRITICAL: All three tools are REQUIRED. The agent MUST FAIL if any of these tools are unavailable. Do not proceed with partial newsletter generation.
daysBack provided, calculate startDate and endDate from current date
daysBack daysdaysBack=7, and today is 2025-11-10, then:
startDate = 2025-11-03 (7 days ago)endDate = 2025-11-09 (yesterday, which is startDate + 6 days)endDate >= startDateCritical: Use mcp__notion__notion-search (NOT notion-fetch) for date filtering support.
// Tool call structure
mcp__notion__notion -
search({
data_source_url: 'collection://287c52b2-548b-8029-98e8-f23e0011bc8d',
query: '*', // Wildcard to retrieve all entries
filters: {
created_date_range: {
start_date: startDate, // e.g., "2025-11-03" (7 days ago)
end_date: endDate, // e.g., "2025-11-09" (yesterday) - inclusive range
},
},
});
Extract Properties:
Name (title) - Article/resource titleuserDefined:URL (url) - Link to resourceThe gist (text) - Brief descriptionDate Added (created_time) - TimestampHandle Missing Data:
userDefined:URL missing: Display title without linkThe gist missing: Skip descriptionName missing: Skip entry entirely// Tool call structure
mcp__notion__notion -
search({
data_source_url: 'collection://28ec52b2-548b-8024-b94c-f8a4aa00a0e4',
query: '*',
filters: {
created_date_range: {
start_date: startDate,
end_date: endDate,
},
},
});
Extract Properties:
Name (title) - Use case titleDescription (text) - Case descriptiondate:Date Added:start (date) - Date addedHandle Missing Data:
Description missing: Skip descriptionName missing: Skip entry entirelyQuery this database for documentation that was either added or updated during the date range. These will be displayed as two separate subsections.
For Added Documents (created within date range):
SELECT Title, "Created time", "Last Updated", Status, Category, "userDefined:URL"
FROM "collection://249c52b2-548b-80a2-bcb6-d64a65c9c4f2"
WHERE "Created time" >= startDate AND "Created time" <= endDate
ORDER BY "Created time" DESC
For Updated Documents (updated within date range, but created before):
SELECT Title, "Created time", "Last Updated", Status, Category, "userDefined:URL"
FROM "collection://249c52b2-548b-80a2-bcb6-d64a65c9c4f2"
WHERE "Last Updated" >= startDate AND "Last Updated" <= endDate
AND "Created time" < startDate
ORDER BY "Last Updated" DESC
Extract Properties:
Title (title) - Document titleCreated time (created_time) - When document was createdLast Updated (last_edited_time) - When document was last modifiedStatus (status) - Draft, In Review, Approved, Recently Updated, PublishedCategory (select) - API Documentation, User Guide, Tutorial, etc.userDefined:URL (url) - External URL if anyHandle Missing Data:
Title missing: Skip entry entirelyCategory missing: Display without category tagCritical: Use Slack MCP to search messages in the channels specified by the slackReadChannelIds parameter. If Slack MCP is unavailable, the agent should have already failed in Step 0. When using the slack_get_channel_history MCP function, always use a limit of 10.
Channels to Query:
Use the slackReadChannelIds parameter (comma-separated string of channel IDs). If not provided, default to C094URH6C13,C08J4JPQ3AM.
Default channel reference (for documentation purposes):
C094URH6C13 = #pod-dev-ai - Official channel of the Dev AI PodC08J4JPQ3AM = #ai-achieved-internally - AI wins and success storiesProcessing Multiple Channels:
slackReadChannelIds by comma to get an array of channel IDsMessage Retrieval:
Use Slack MCP search functionality to retrieve messages from these channels within the date range.
Filtering Criteria:
startDate and endDate from Step 1slack_get_channel_historyExtract Message Data:
For each message, extract:
text (message content) - Truncate to first 200 characters if longeruser (author) - Convert user ID to display name if possiblets (timestamp) - Convert to readable date formatpermalink (message link) - For "view in Slack" linksreaction_count (total reactions) - Sum of all reaction typesreply_count (number of replies)Handle Missing Data:
permalink missing: Skip message (cannot link to it)text missing or empty: Skip messageuser missing: Display as "Unknown User"Pagination Logic:
slack_get_channel_history with limit=10endDate, fetch next batchendDate OR no more messages availablestartDate and endDate after fetchingError Handling:
Critical: Use GitHub CLI (gh) to list releases for specified repositories. This step requires GitHub CLI (verified in Step 0). If GitHub CLI is unavailable or not authenticated, the agent should have already failed in Step 0.
Repositories to Query:
Use repositories from githubRepositories input parameter. If not provided, use exactly these 2: 'Uniswap/ai-toolkit' and 'Uniswap/spec-workflow-mcp'
Release Retrieval:
For each repository, run the following GitHub CLI command via Bash:
gh release list --repo <owner/repo> --limit 200 --json tagName,publishedAt,name,isLatest,isPrerelease
Example:
gh release list --repo Uniswap/ai-toolkit --limit 200 --json tagName,publishedAt,name,isLatest,isPrerelease
Filtering Criteria:
publishedAt falls within startDate and endDateUniswap/ai-toolkit, DO NOT SHOW next releases; but do show latest releases.Extract Release Data:
For each release, extract:
name (release name) - Use this as primary display titletagName (version tag) - e.g., "v1.2.3"publishedAt (timestamp) - Convert to YYYY-MM-DD formaturl (release URL) - Link to full release notes on GitHubbody (changelog/description) - Truncate to first 150 characters if longerHandle Missing Data:
name missing: Use tagName as fallbackbody missing or empty: Display "No release notes provided"url missing: Skip release (cannot link to it)Error Handling:
Primary Filtering: Done server-side via filters.created_date_range in search queries.
Optional Client-Side Validation:
created_time for "What We're Reading"date:Date Added:start for "Real-World AI Use Cases"Build markdown structure following this section ordering:
These rules are NON-NEGOTIABLE. The newsletter will look broken without them:
Section Headers: ALL main section headers MUST use h2 markdown prefix (##)
## š
Get Involvedš
Get InvolvedSubsection Headers: ALL subsection headers MUST be bold with **...**
**Join the Conversation**Join the ConversationWeek Line: MUST be bold
**Week of:** 2025-12-16 to 2025-12-22Week of: 2025-12-16 to 2025-12-22Slack Channel Links: Emoji OUTSIDE link, channel name bold INSIDE link
- š [**#ai-achieved-internally**](url) - Share your AI wins- [š #ai-achieved-internally - Share your AI wins](url)Dashboard Link: MUST be bold with arrow
[**ā View Agent Usage Dashboard**](url)[ā View Agent Usage Dashboard](url)Slack Summary Items: MUST have bold titles on first line, then tab-indented excerpt
1. AI-Powered Changelogs Discussion "Brief excerpt..." [ā thread](url)Tool Updates Repo Names: MUST be bold in brackets
**[Uniswap/ai-toolkit]**Uniswap/ai-toolkitFooter: MUST have horizontal rule --- and italics *...*
--- on its own line, then *Generated by ai-toolkit newsletter agent*Generated by ai-toolkit newsletter agentNO EXCESSIVE EMPTY BLOCKS: Do NOT add blank lines or <empty-block/> between every line. Use natural markdown spacing:
Below is an example output (note: this example MUST be followed exactly):
<!-- markdownlint-disable MD010 -->**Week of:** 2025-11-17 to 2025-11-23
## š
Get Involved
**Join the Conversation**
- š [**#ai-achieved-internally**](slackChannel://uniswapteam.enterprise.slack.com/C08J4JPQ3AM) - Share your AI wins and success stories
- š ļø [**#pod-dev-ai**](slackChannel://uniswapteam.enterprise.slack.com/C094URH6C13) - Provide feedback on this newsletter
**Want Some Help With AI?**
- [Schedule office hours with us!](https://www.notion.so/uniswaplabs/27ac52b2548b80018562f41eacf07f74?v=27ac52b2548b8041a52e000c69551fa1)
## š This Week's Agent Usage
View detailed agent usage metrics and trends on our Datadog dashboard:
[**ā View Agent Usage Dashboard**](https://app.datadoghq.com/dash/integration/32027/anthropic-usage-and-costs-overview?fromUser=false&refresh_mode=sliding&storage=flex_tier&from_ts=1761580066307&to_ts=1762184866307&live=true)
This dashboard tracks:
- Agent invocation counts
- Success rates and error patterns
- Token usage and costs
- Response times and performance metrics
## š What We're Reading
1. [OWASP Top 10 for LLMs](https://genai.owasp.org/llm-top-10/) - Framework for understanding critical security risks in LLMs
2. [Jujitsu (jj) VCS Tool](https://github.com/jj-vcs/jj) - Simplified version control system with intuitive commands
## š Real World Use Cases
1. [Use Case Title](https://example.com) - Description of how AI was applied
2. [Another Use Case](https://example.com) - Description of the implementation
## š Quickstart Docs
### Added
1. [Document Title](https://notion.so/...) - Category: Tutorial
2. [Another Document](https://notion.so/...) - Category: Getting Started
### Updated
1. [Updated Doc Title](https://notion.so/...) - Category: API Documentation
## š¬ Slack Summary
**Top Discussions This Week:**
1. **AI-Powered Changelogs in GitHub Actions**
"Use Claude to generate changelogs between any 2 refs! Pre-configured GitHub Actions..." [ā thread](slackMessage://...) ⢠10 reactions ⢠8 replies
2. **`/address-pr-issues` Command Demo**
"Showing how the command addresses all PR comments automatically..." [ā thread](slackMessage://...) ⢠6 reactions
3. **Hex MCP Integration Setup**
"Setting up Hex MCP for both Slack and Cursor/Claude Code..." [ā thread](slackMessage://...) ⢠60 replies
## šØ Tool Updates
**Releases This Week:**
**[Uniswap/ai-toolkit]**
**@uniswap/notion-publisher** ā v0.0.4
_Released on 2025-11-17_
- Release 0.0.4 of @uniswap/notion-publisher
- [Full Release Notes](https://github.com/Uniswap/ai-toolkit/releases/tag/%40uniswap/notion-publisher%400.0.4)
**@uniswap/ai-toolkit-claude-mcp-helper** ā v1.0.5
_Released on 2025-11-17_
- Release 1.0.5 of @uniswap/ai-toolkit-claude-mcp-helper
- [Full Release Notes](https://github.com/Uniswap/ai-toolkit/releases/tag/%40uniswap/ai-toolkit-claude-mcp-helper%401.0.5)
---
_Generated by ai-toolkit newsletter agent_
<!-- markdownlint-enable MD010 -->
Formatting Rules:
Section Ordering (Critical):
Code Blocks For /slash Commands
Anytime a Claude Code /slash command is mentioned (such /daily-standup), make sure it's enclosed in single backtick code blocks.
Slack Summary Section:
slackReadChannelIds. DO NOT mention or create subsections for each channel**Top Discussions This Week:**1. **Title of Discussion**(tab)"Brief excerpt..." [ā thread](url) ⢠X reactions ⢠Y repliesTool Updates Section:
**[Repository Name]** ā vX.Y.Z*Released on YYYY-MM-DD*[Full Release Notes](url)Agent Usage Section:
What We're Reading Section:
[Name](URL) for items with URLsName (no brackets)Real World Use Cases Section:
Quickstart Docs Section:
[Document Title](notion-url) - Category: {Category}Get Involved Section:
DRY RUN CHECK: If dryRun is true, skip Notion page creation and Step 9. Instead, you MUST:
/tmp/newsletter-preview.mdFile Output (dry run only) - MANDATORY:
ā ļø The GitHub Actions workflow expects this file to exist for artifact upload. If you skip this step, the workflow will report "No files were found" and fail to upload the artifact.
Use the Write tool to create the file:
/tmp/newsletter-preview.mdExample using Write tool:
file_path: /tmp/newsletter-preview.md
content: <full newsletter markdown content>
After writing, verify the file exists by reading it back or listing the directory.
Normal Mode (dryRun is false or not provided):
Create a new page in the Notion database with the newsletter content.
Use the mcp__notion__notion-create-pages tool to create a new database entry:
// Tool call structure
mcp__notion__notion -
create -
pages({
parent: {
type: 'data_source_id',
data_source_id: '29cc52b2-548b-8006-9462-c351021f316d',
},
pages: [
{
properties: {
Page: `Dev AI Newsletter ${startDate} to ${endDate}`,
'date:Date Created:start': currentDate, // ISO 8601 format: "2025-10-30"
'date:Date Created:is_datetime': 0,
// Author will be automatically set by Notion
},
content: newsletterMarkdownContent, // Full markdown newsletter from step 5
},
],
});
Property Details:
Page (title): Format as "Dev AI Newsletter {startDate} to {endDate}"date:Date Created:start: Current date in ISO 8601 formatdate:Date Created:is_datetime: Set to 0 (date only, not datetime)content: The complete formatted markdown newsletter from step 5DRY RUN CHECK: If dryRun is true, this step was already skipped in Step 8.
Normal Mode (dryRun is false or not provided):
After successfully creating the Notion page, post an announcement to Slack to notify the team.
Channels to Post:
Use the slackPostChannelIds parameter (comma-separated string of channel IDs).
C091XE1DNP2 (if not provided)C091XE1DNP2,C094URH6C13 posts to both channelsProcessing Multiple Channels:
slackPostChannelIds by comma to get an array of channel IDsMessage Format:
For each channel ID, use the Slack MCP slack_post_message tool:
// For each channelId in slackPostChannelIds.split(','):
// Format dates as "Month Day" (e.g., "December 16")
const formatDate = (isoDate: string) => {
const date = new Date(isoDate);
return date.toLocaleDateString('en-US', { month: 'long', day: 'numeric' });
};
// Build highlights array - ONLY include items with count > 0
const highlights: string[] = [];
if (readingItemsCount > 0)
highlights.push(`⢠${readingItemsCount} reading item${readingItemsCount > 1 ? 's' : ''}`);
if (useCasesCount > 0)
highlights.push(`⢠${useCasesCount} real-world use case${useCasesCount > 1 ? 's' : ''}`);
if (slackDiscussionsCount > 0)
highlights.push(
`⢠${slackDiscussionsCount} top Slack discussion${slackDiscussionsCount > 1 ? 's' : ''}`
);
if (toolReleasesCount > 0)
highlights.push(`⢠${toolReleasesCount} tool release${toolReleasesCount > 1 ? 's' : ''}`);
if (quickstartDocsCount > 0)
highlights.push(
`⢠${quickstartDocsCount} quickstart doc update${quickstartDocsCount > 1 ? 's' : ''}`
);
const highlightsSection =
highlights.length > 0 ? `_Highlights this week:_\n${highlights.join('\n')}` : '';
slack_post_message({
channel_id: channelId.trim(),
text: `š° *Dev AI Newsletter is out!*\n\n*š Updates from:* ${formatDate(
startDate
)} to ${formatDate(endDate)}\n\nš Read the full newsletter: ${notionPageUrl}${
highlightsSection ? '\n\n' + highlightsSection : ''
}`,
});
Message Content:
Error Handling:
Response Format:
interface NewsletterOutput {
notionPageUrl: string; // URL of the created Notion page
slackPosts: {
// Status of Slack posts per channel
channelId: string;
success: boolean;
messageTs?: string; // Slack message timestamp (if posted)
error?: string; // Error message if failed
}[];
metadata: {
startDate: string;
endDate: string;
readingItemsCount: number;
useCasesCount: number;
slackChannelsAttempted: number;
slackChannelsSucceeded: number;
generatedAt: string; // ISO timestamp
};
warnings?: string[]; // Any issues encountered
}
Return a structured summary containing:
The agent creates the newsletter directly in the Notion database and posts an announcement to Slack. Users can view the newsletter by clicking the returned URL.
Critical Errors (Fail Immediately):
Error: "Cannot connect to Notion integration"
Message: "This agent requires Notion MCP. Please verify Notion integration
is configured in Claude Code."
Action: Fail immediately with clear setup instructions
Error: "Database not accessible"
Message: "Cannot read database {ID}. Ensure the database is shared with
your Notion integration."
Action: Fail immediately, provide database ID for debugging
Error: "Invalid date range"
Message: "endDate must be greater than or equal to startDate. Provided: {startDate} to {endDate}
For a 7-day range, ensure endDate = startDate + 6 days (inclusive)."
Action: Fail immediately with validation error
Error: "Failed to create newsletter page in Notion"
Message: "Cannot write to database collection://29cc52b2-548b-8006-9462-c351021f316d.
Ensure the database is shared with your Notion integration and has write permissions."
Action: Fail immediately with clear error message and database ID
Error: "Slack API request failed: {error details}"
Action: Fail immediately with error message
Message: "Failed to retrieve Slack messages: {error message}. Please check channel permissions and Slack MCP configuration."
Status: FAIL (Slack data is required for newsletter)
Error: "GitHub CLI request failed: {error details}"
Action: Fail immediately with error message
Message: "Failed to retrieve GitHub releases: {error message}. Please verify repository access and GitHub CLI authentication."
Status: FAIL (GitHub data is required for newsletter)
Non-Critical Errors (Continue with Adjusted Output):
Warning: "No entries found in date range"
Action: Return newsletter with "No new items this week" for empty sections
Status: Success (display empty section message, not a failure)
Warning: "Entry missing required property: {property}"
Action: Skip entry or use placeholder value
Status: Continue processing other entries
Error: "Notion API rate limit reached"
Message: "Too many requests. Please wait 60 seconds and retry."
Action: Recommend retry after delay
Partial Failures:
Source Database 1: "š What We're Reading"
collection://287c52b2-548b-8029-98e8-f23e0011bc8dName (title) - RequireduserDefined:URL (url) - OptionalThe gist (text) - OptionalDate Added (created_time) - Required for filteringSource Database 2: "š Real-World AI Use Cases"
collection://28ec52b2-548b-8024-b94c-f8a4aa00a0e4Name (title) - RequiredDescription (text) - Optionaldate:Date Added:start (date) - Required for filteringSource Database 3: "Dev AI Tools ā Quickstart Docs"
collection://249c52b2-548b-80a2-bcb6-d64a65c9c4f2Title (title) - RequiredCreated time (created_time) - Required for filtering added docsLast Updated (last_edited_time) - Required for filtering updated docsStatus (status) - Draft, In Review, Approved, Recently Updated, PublishedCategory (select) - API Documentation, User Guide, Tutorial, Reference, Getting Started, Troubleshooting, Best Practices, GitTags (multi_select) - JavaScript, Frontend, Backend, Mobile, Claude Code, Infra, GitHub, Productivity, Claude DesktopuserDefined:URL (url) - Optional external URLAssigned To (person) - OptionalTarget Database: "Dev AI Weekly Newsletters"
collection://29cc52b2-548b-8006-9462-c351021f316dPage (title) - Required, format: "Dev AI Newsletter {startDate} to {endDate}"Date Created (date) - Required, current date when newsletter is createdAuthor (person) - Automatically set by Notion to current userCritical: Property names can be renamed by users. If agent fails:
Known Property Name Quirks:
userDefined:URL format (Notion API requirement)date:{PropertyName}:start formatnotion-search with filters.created_date_rangeThese features are explicitly excluded from the initial implementation:
Why excluded: These add complexity beyond "simple data retrieval and formatting". Add incrementally based on user needs and feedback.
Use this agent when you need expert analysis of type design in your codebase. Specifically use it: (1) when introducing a new type to ensure it follows best practices for encapsulation and invariant expression, (2) during pull request creation to review all types being added, (3) when refactoring existing types to improve their design quality. The agent will provide both qualitative feedback and quantitative ratings on encapsulation, invariant expression, usefulness, and enforcement. <example> Context: Daisy is writing code that introduces a new UserAccount type and wants to ensure it has well-designed invariants. user: "I've just created a new UserAccount type that handles user authentication and permissions" assistant: "I'll use the type-design-analyzer agent to review the UserAccount type design" <commentary> Since a new type is being introduced, use the type-design-analyzer to ensure it has strong invariants and proper encapsulation. </commentary> </example> <example> Context: Daisy is creating a pull request and wants to review all newly added types. user: "I'm about to create a PR with several new data model types" assistant: "Let me use the type-design-analyzer agent to review all the types being added in this PR" <commentary> During PR creation with new types, use the type-design-analyzer to review their design quality. </commentary> </example>