RSS zu 66 sozialen Netzwerken: Facebook, Instagram, X, LinkedIn, Telegram und mehr Blog Affiliate Kontakt
Anmelden Kostenlos starten
Updated: 2026-10-17
AWS Lambda RSS Feed: Serve It From DynamoDB and Auto-Post

Short answer: To serve an RSS feed from AWS Lambda, write a small Python handler that queries a DynamoDB index for the newest published items, builds RSS 2.0 XML with escaped text, absolute links and RFC 822 dates, and returns it with the content type application/rss+xml and a cache header. Expose the function through a Function URL or an API Gateway route, put a cache in front of it, validate the output and give the public address to an auto-poster so each new item is shared on social media automatically.

Serverless sites are common now: a static front end on a content delivery network, an API on Lambda and a table in DynamoDB. They are fast and cheap to run, but they have no built-in feed, because there is no CMS to provide one. The feed has to be a small function of its own. That is a good fit for Lambda, because a feed is read-only, stateless and easy to cache. This guide builds the function step by step, explains the settings that matter and covers the traps that appear only in serverless setups, such as cold starts, time zones and the address that visitors really see.

The architecture in one picture

The design has four parts, and each one is small:

  • DynamoDB table. It stores one item per post, with an identifier, a title, a slug, a summary, a status, a publication date and an optional image address.
  • Global secondary index. It uses the status as the partition key and the publication date as the sort key, so the newest published posts can be read in order without scanning the table.
  • Lambda function. It reads twenty items from the index, builds the XML and returns it.
  • Public entry point and cache. A Function URL or an API Gateway route exposes the function, and a content delivery network keeps a copy for a few minutes.

If your content is not in DynamoDB, the same pattern works with any data source that the function can reach. Only the query changes.

Prepare the table and the index

A scan reads the whole table, which becomes slow and costly as the archive grows, so use an index from the start. Create a global secondary index named status-published_at-index with the partition key status (string) and the sort key published_at (string). Store dates as ISO 8601 text in UTC, for example 2026-10-17T08:30:00+00:00. Text in this format sorts in chronological order, which is exactly what the query needs. Store the status as the word published for live items and anything else for drafts, so the query returns only public content. Scheduled items are handled by the date condition in the query.

The handler

Create a file named handler.py. It uses only the standard library and boto3, which the Python runtime already includes.

import os
from datetime import datetime, timezone
from email.utils import format_datetime
from xml.sax.saxutils import escape

import boto3
from boto3.dynamodb.conditions import Key

TABLE = os.environ["TABLE_NAME"]
SITE = os.environ["SITE_URL"].rstrip("/")
LIMIT = 20

table = boto3.resource("dynamodb").Table(TABLE)


def rfc822(value):
    moment = datetime.fromisoformat(value)
    if moment.tzinfo is None:
        moment = moment.replace(tzinfo=timezone.utc)
    return format_datetime(moment.astimezone(timezone.utc), usegmt=True)


def build_item(row):
    link = SITE + "/blog/" + row["slug"]
    parts = [
        "<item>",
        "<title>" + escape(row["title"]) + "</title>",
        "<link>" + escape(link) + "</link>",
        '<guid isPermaLink="false">post-' + escape(row["id"]) + "</guid>",
        "<pubDate>" + rfc822(row["published_at"]) + "</pubDate>",
        "<description>" + escape(row.get("summary", "")) + "</description>",
    ]
    if row.get("image_url"):
        parts.append('<enclosure url="' + escape(row["image_url"]) + '" type="image/jpeg" length="0"/>')
    parts.append("</item>")
    return "".join(parts)


def handler(event, context):
    now = datetime.now(timezone.utc).isoformat()
    result = table.query(
        IndexName="status-published_at-index",
        KeyConditionExpression=Key("status").eq("published") & Key("published_at").lte(now),
        ScanIndexForward=False,
        Limit=LIMIT,
    )
    rows = result.get("Items", [])
    if not rows:
        return {"statusCode": 503, "headers": {"Retry-After": "300"}, "body": "Feed temporarily unavailable"}

    xml = (
        '<?xml version="1.0" encoding="UTF-8"?>'
        '<rss version="2.0"><channel>'
        "<title>Example Blog</title>"
        "<link>" + SITE + "/blog</link>"
        "<description>News and articles from Example</description>"
        + "".join(build_item(r) for r in rows)
        + "</channel></rss>"
    )
    return {
        "statusCode": 200,
        "headers": {
            "Content-Type": "application/rss+xml; charset=utf-8",
            "Cache-Control": "public, max-age=300",
        },
        "body": xml,
    }

Several choices are worth explaining. The table name and the site address come from environment variables, so the same code runs in a test stage and in production. The query is sorted newest first with ScanIndexForward=False and stops at twenty items. The date condition keeps scheduled items out of the feed until their time. And when the query returns nothing, the function answers with a 503 and a retry hint instead of an empty feed, because an empty file can look like a deletion to some tools.

Deploy the function

  1. Create a function with a current Python runtime and set the handler to handler.handler.
  2. Add the two environment variables, TABLE_NAME und SITE_URL.
  3. Give the execution role permission to query the table and its index, and nothing more. A feed never writes data.
  4. Set the timeout to a few seconds and the memory to a modest value. The function does very little work.
  5. Test it in the console with an empty event and check that the response contains XML.

Keep permissions narrow. The role should allow only the query action on this table and its index. If the function is ever misconfigured or the code is copied elsewhere, the damage stays small.

Choose the public entry point

There are two common ways to expose the function. A Function URL is the simplest: enable it with no authentication, and you receive an address that returns the response. It is a good choice for a feed, because the content is public anyway. An API Gateway HTTP API gives you a custom path such as /feed.xml, throttling and easier use of custom domains. In both cases the response format is the same: a status code, headers and a string body.

Visitors and auto-posters should never see the long generated address. Put the function behind your own domain, so that the feed lives at a stable address such as https://www.example.com/feed.xml. Many teams do this with a content delivery network that forwards the path /feed.xml to the function. A stable address matters because an auto-poster stores it. If you change it later, you have to reconnect every source and you risk duplicate posts.

Caching without delaying your posts

The header in the code lets a content delivery network keep a copy for five minutes. That removes almost all load from the function and the table, because thousands of requests share one result. It also adds a delay of up to five minutes between publishing and appearing in the feed, which is acceptable for blogs and announcements. Do not use hours: a long lifetime delays every social post by the same amount, as explained in RSS feed caching explained. If you can invalidate the cache from your publishing workflow, you can use a longer lifetime and clear it whenever a new post goes live. Conditional requests are another helpful tool; see ETags and conditional GET.

Dates, escaping and identifiers

The same three rules apply to every generated feed, and serverless code is not an exception:

Cold starts and timeouts

When a function has not run for a while, the first request waits for the runtime to start. For a small Python function this is usually a fraction of a second, but it can add up if the package is large. Keep the deployment small, create the boto3 client outside the handler as the example does, and avoid heavy imports. Auto-posters wait a limited time for a feed, so a slow response is a risk; see RSS feed timeouts. With a content delivery network in front, only a cache miss pays the cold start, and most requests never reach the function.

Test the feed before connecting it

  1. Request the public address with curl -i and check the status, the content type and the cache header; the method is described in debugging an RSS feed with curl.
  2. Run the file through the W3C Feed Validation Service.
  3. Add a test item, wait for the cache to expire and confirm that it appears first.
  4. Add an item with a future date and confirm that it stays hidden.
  5. Check that every link in the file opens the real page.

An alternative: generate the file on a schedule

If you prefer not to run code on every request, flip the design. A scheduled rule runs the function every ten minutes, builds the XML and writes it to an object storage bucket as feed.xml. The content delivery network serves that file like any static asset. The feed is then as fresh as the last run, there is no query per visitor and the failure mode is gentle: if the job fails, the old file stays online. The cost is a small delay, and you need a check that warns you when the file becomes too old.

Common mistakes to avoid

  • Scanning the table. Use an index; a scan gets slower with every post.
  • Publishing the generated address. Put the feed behind your own domain so that it never changes.
  • Returning an empty feed on errors. Return an error status instead and let the old copy stay in the cache.
  • Permissions that are too wide. Allow only the query action on the one table.
  • Forgetting the cache. Without one, a burst of requests becomes a burst of database reads.

How PostRSS works with a Lambda feed

PostRSS has been publishing RSS and Atom feeds to social networks since 2014. Paste the public address of your feed, connect your accounts and choose how each post is built from the title, the description or the page title. PostRSS checks the feed as often as every minute and shares each new item, with its image and link, to the channels you choose among 66 supported networks, messengers, team chats and blogs, including LinkedIn, X, Bluesky, Mastodon and Telegram. Keyword filters, UTM parameters and posting windows let you tune each channel. See the PostRSS features page and the plan limits on the pricing page. More developer guides are under the Technical Guide tag.

Weiterführende Lektüre

The bottom line

A serverless feed is a small, cheap and dependable function. Keep published items in an indexed table, query only the newest ones, escape every value, use stable identifiers, convert dates to RFC 822 in UTC and return the XML with the right content type and a short cache. Put the function behind your own domain so the address never changes, test it with curl and a validator and connect it to an auto-poster. After that, each post you publish can reach your social channels without extra work.

FAQ

Is Lambda a good place to serve an RSS feed?

Yes, especially with a cache in front. A feed is read-only and small, so a short function plus a content delivery network handles even large audiences without servers to maintain.

Do I need API Gateway or is a Function URL enough?

A Function URL is enough for a public feed. API Gateway adds custom paths, throttling and other features, which are useful if you already use it for the rest of your API.

How do I avoid scanning the whole DynamoDB table?

Create a global secondary index with the status as the partition key and the publication date as the sort key, then query it newest first with a limit of twenty items.

How fresh will the feed be?

As fresh as the cache lifetime allows. With a lifetime of five minutes, a new post appears in the feed within that time, which is fine for most blogs and announcement pages.

Can I connect the Lambda feed to PostRSS?

Yes. Add the public feed address as a source in PostRSS, connect your social accounts and each new item is posted automatically. Make sure the address opens without a login.

{“@context”: “https://schema.org”, “@type”: “FAQPage”, “mainEntity”: [{“@type”: “Question”, “name”: “Is Lambda a good place to serve an RSS feed?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “Yes, especially with a cache in front. A feed is read-only and small, so a short function plus a content delivery network handles even large audiences without servers to maintain.”}}, {“@type”: “Question”, “name”: “Do I need API Gateway or is a Function URL enough?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “A Function URL is enough for a public feed. API Gateway adds custom paths, throttling and other features, which are useful if you already use it for the rest of your API.”}}, {“@type”: “Question”, “name”: “How do I avoid scanning the whole DynamoDB table?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “Create a global secondary index with the status as the partition key and the publication date as the sort key, then query it newest first with a limit of twenty items.”}}, {“@type”: “Question”, “name”: “How fresh will the feed be?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “As fresh as the cache lifetime allows. With a lifetime of five minutes, a new post appears in the feed within that time, which is fine for most blogs and announcement pages.”}}, {“@type”: “Question”, “name”: “Can I connect the Lambda feed to PostRSS?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “Yes. Add the public feed address as a source in PostRSS, connect your social accounts and each new item is posted automatically. Make sure the address opens without a login.”}}]}

New guides, once a month

What changed in the networks, what broke, and how to fix it before it costs you reach.

We send a confirmation e-mail first. Unsubscribe any time.

Mehr Tools von unserem Team

Entwickelt von Internet Solutions, dem Team hinter PostRSS. Jedes Produkt spart Ihnen auf seine Weise Zeit.