Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions apps/docs/components/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4170,3 +4170,32 @@ export function DuckDuckGoIcon(props: SVGProps<SVGSVGElement>) {
</svg>
)
}

export function RssIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
{...props}
width='24'
height='24'
viewBox='0 0 24 24'
fill='none'
xmlns='http://www.w3.org/2000/svg'
>
<path
d='M4 11C6.38695 11 8.67613 11.9482 10.364 13.636C12.0518 15.3239 13 17.6131 13 20'
stroke='currentColor'
strokeWidth='2'
strokeLinecap='round'
strokeLinejoin='round'
/>
<path
d='M4 4C8.24346 4 12.3131 5.68571 15.3137 8.68629C18.3143 11.6869 20 15.7565 20 20'
stroke='currentColor'
strokeWidth='2'
strokeLinecap='round'
strokeLinejoin='round'
/>
<circle cx='5' cy='19' r='1' fill='currentColor' />
</svg>
)
}
4 changes: 4 additions & 0 deletions apps/docs/content/docs/en/triggers/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ Use the Start block for everything originating from the editor, deploy-to-API, o
<Card title="Schedule" href="/triggers/schedule">
Cron or interval based execution
</Card>
<Card title="RSS Feed" href="/triggers/rss">
Monitor RSS and Atom feeds for new content
</Card>
</Cards>

## Quick Comparison
Expand All @@ -39,6 +42,7 @@ Use the Start block for everything originating from the editor, deploy-to-API, o
| **Start** | Editor runs, deploy-to-API requests, or chat messages |
| **Schedule** | Timer managed in schedule block |
| **Webhook** | On inbound HTTP request |
| **RSS Feed** | New item published to feed |

> The Start block always exposes `input`, `conversationId`, and `files` fields. Add custom fields to the input format for additional structured data.

Expand Down
2 changes: 1 addition & 1 deletion apps/docs/content/docs/en/triggers/meta.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"pages": ["index", "start", "schedule", "webhook"]
"pages": ["index", "start", "schedule", "webhook", "rss"]
}
49 changes: 49 additions & 0 deletions apps/docs/content/docs/en/triggers/rss.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
---
title: RSS Feed
---

import { Callout } from 'fumadocs-ui/components/callout'
import { Image } from '@/components/ui/image'

The RSS Feed block monitors RSS and Atom feeds – when new items are published, your workflow triggers automatically.

<div className="flex justify-center">
<Image
src="/static/blocks/rss.png"
alt="RSS Feed Block"
width={500}
height={400}
className="my-6"
/>
</div>

## Configuration

1. **Add RSS Feed Block** - Drag the RSS Feed block to start your workflow
2. **Enter Feed URL** - Paste the URL of any RSS or Atom feed
3. **Deploy** - Deploy your workflow to activate polling

Once deployed, the feed is checked every minute for new items.

## Output Fields

| Field | Type | Description |
|-------|------|-------------|
| `title` | string | Item title |
| `link` | string | Item link |
| `pubDate` | string | Publication date |
| `item` | object | Raw item with all fields |
| `feed` | object | Raw feed metadata |

Access mapped fields directly (`<rss.title>`) or use the raw objects for any field (`<rss.item.author>`, `<rss.feed.language>`).

## Use Cases

- **Content monitoring** - Track blogs, news sites, or competitor updates
- **Podcast automation** - Trigger workflows when new episodes drop
- **Release tracking** - Monitor GitHub releases, changelogs, or product updates
- **Social aggregation** - Collect content from platforms that expose RSS feeds

<Callout>
RSS triggers only fire for items published after you save the trigger. Existing feed items are not processed.
</Callout>
Binary file added apps/docs/public/static/blocks/rss.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
66 changes: 66 additions & 0 deletions apps/sim/app/api/webhooks/poll/rss/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { nanoid } from 'nanoid'
import { type NextRequest, NextResponse } from 'next/server'
import { verifyCronAuth } from '@/lib/auth/internal'
import { acquireLock, releaseLock } from '@/lib/core/config/redis'
import { createLogger } from '@/lib/logs/console/logger'
import { pollRssWebhooks } from '@/lib/webhooks/rss-polling-service'

const logger = createLogger('RssPollingAPI')

export const dynamic = 'force-dynamic'
export const maxDuration = 180 // Allow up to 3 minutes for polling to complete

const LOCK_KEY = 'rss-polling-lock'
const LOCK_TTL_SECONDS = 180 // Same as maxDuration (3 min)

export async function GET(request: NextRequest) {
const requestId = nanoid()
logger.info(`RSS webhook polling triggered (${requestId})`)

let lockValue: string | undefined

try {
const authError = verifyCronAuth(request, 'RSS webhook polling')
if (authError) {
return authError
}

lockValue = requestId
const locked = await acquireLock(LOCK_KEY, lockValue, LOCK_TTL_SECONDS)

if (!locked) {
return NextResponse.json(
{
success: true,
message: 'Polling already in progress – skipped',
requestId,
status: 'skip',
},
{ status: 202 }
)
}

const results = await pollRssWebhooks()

return NextResponse.json({
success: true,
message: 'RSS polling completed',
requestId,
status: 'completed',
...results,
})
} catch (error) {
logger.error(`Error during RSS polling (${requestId}):`, error)
return NextResponse.json(
{
success: false,
message: 'RSS polling failed',
error: error instanceof Error ? error.message : 'Unknown error',
requestId,
},
{ status: 500 }
)
} finally {
await releaseLock(LOCK_KEY).catch(() => {})
}
}
37 changes: 37 additions & 0 deletions apps/sim/app/api/webhooks/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,43 @@ export async function POST(request: NextRequest) {
}
// --- End Outlook specific logic ---

// --- RSS webhook setup ---
if (savedWebhook && provider === 'rss') {
logger.info(`[${requestId}] RSS provider detected. Setting up RSS webhook configuration.`)
try {
const { configureRssPolling } = await import('@/lib/webhooks/utils.server')
const success = await configureRssPolling(savedWebhook, requestId)

if (!success) {
logger.error(`[${requestId}] Failed to configure RSS polling, rolling back webhook`)
await db.delete(webhook).where(eq(webhook.id, savedWebhook.id))
return NextResponse.json(
{
error: 'Failed to configure RSS polling',
details: 'Please try again',
},
{ status: 500 }
)
}

logger.info(`[${requestId}] Successfully configured RSS polling`)
} catch (err) {
logger.error(
`[${requestId}] Error setting up RSS webhook configuration, rolling back webhook`,
err
)
await db.delete(webhook).where(eq(webhook.id, savedWebhook.id))
return NextResponse.json(
{
error: 'Failed to configure RSS webhook',
details: err instanceof Error ? err.message : 'Unknown error',
},
{ status: 500 }
)
}
}
// --- End RSS specific logic ---

const status = targetWebhookId ? 200 : 201
return NextResponse.json({ webhook: savedWebhook }, { status })
} catch (error: any) {
Expand Down
36 changes: 36 additions & 0 deletions apps/sim/blocks/blocks/rss.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { RssIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import { getTrigger } from '@/triggers'

export const RssBlock: BlockConfig = {
type: 'rss',
name: 'RSS Feed',
description: 'Monitor RSS feeds and trigger workflows when new items are published',
longDescription:
'Subscribe to any RSS or Atom feed and automatically trigger your workflow when new content is published. Perfect for monitoring blogs, news sites, podcasts, and any content that publishes an RSS feed.',
category: 'triggers',
bgColor: '#F97316',
icon: RssIcon,
triggerAllowed: true,

subBlocks: [...getTrigger('rss_poller').subBlocks],

tools: {
access: [], // Trigger-only for now
},

inputs: {},

outputs: {
title: { type: 'string', description: 'Item title' },
link: { type: 'string', description: 'Item link' },
pubDate: { type: 'string', description: 'Publication date' },
item: { type: 'json', description: 'Raw item object with all fields' },
feed: { type: 'json', description: 'Raw feed object with all fields' },
},

triggers: {
enabled: true,
available: ['rss_poller'],
},
}
2 changes: 2 additions & 0 deletions apps/sim/blocks/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ import { RedditBlock } from '@/blocks/blocks/reddit'
import { ResendBlock } from '@/blocks/blocks/resend'
import { ResponseBlock } from '@/blocks/blocks/response'
import { RouterBlock } from '@/blocks/blocks/router'
import { RssBlock } from '@/blocks/blocks/rss'
import { S3Block } from '@/blocks/blocks/s3'
import { SalesforceBlock } from '@/blocks/blocks/salesforce'
import { ScheduleBlock } from '@/blocks/blocks/schedule'
Expand Down Expand Up @@ -229,6 +230,7 @@ export const registry: Record<string, BlockConfig> = {
reddit: RedditBlock,
resend: ResendBlock,
response: ResponseBlock,
rss: RssBlock,
router: RouterBlock,
s3: S3Block,
salesforce: SalesforceBlock,
Expand Down
29 changes: 29 additions & 0 deletions apps/sim/components/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4170,3 +4170,32 @@ export function DuckDuckGoIcon(props: SVGProps<SVGSVGElement>) {
</svg>
)
}

export function RssIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
{...props}
width='24'
height='24'
viewBox='0 0 24 24'
fill='none'
xmlns='http://www.w3.org/2000/svg'
>
<path
d='M4 11C6.38695 11 8.67613 11.9482 10.364 13.636C12.0518 15.3239 13 17.6131 13 20'
stroke='currentColor'
strokeWidth='2'
strokeLinecap='round'
strokeLinejoin='round'
/>
<path
d='M4 4C8.24346 4 12.3131 5.68571 15.3137 8.68629C18.3143 11.6869 20 15.7565 20 20'
stroke='currentColor'
strokeWidth='2'
strokeLinecap='round'
strokeLinejoin='round'
/>
<circle cx='5' cy='19' r='1' fill='currentColor' />
</svg>
)
}
Loading