Skip to content

ouuan/yaf2m

Repository files navigation

yaf2m (Yet Another Feed to Mail)

Send email alerts or digests when your RSS/Atom feeds update.

Features

  • Feed Grouping & Deduplication: Combine multiple feed URLs into a single group. Items from all feeds in a group are deduplicated and processed together.
  • Digest or Individual Emails: Choose between sending a single digest email for multiple updates or separate emails for each new item. Automatic digesting if too many updates.
  • Flexible Per-Feed Settings: Settings (recipients, templates, update keys, etc.) can be set globally or overridden for each feed group.
  • Custom Update Keys: Detect updates using traditional GUIDs or any custom content via MiniJinja expressions, allowing notification on any change you care about.
  • Customizable Email Templates: Use MiniJinja templates for email subject and body.
  • Advanced Filtering: Filter feed items using logical combinations (and/all, or/any, not), regular expressions, or MiniJinja expressions for fine-grained control.
  • Notification On Error: Send notifications when feeds are not working.
  • HTML Sanitization: Sanitize feed HTML content for safer emails.

Quick Start

  • Write a config file with your feeds.
  • Set the environment variables described in the next section.
  • Start the service with Docker Compose: docker compose up -d (see docker-compose.yml).

Environment Variables

Config File

Note: The config file is auto-reloaded. There is no need to restart the service.

Import

You can use opml-to-config.py to convert an OPML file to yaf2m config.

Examples

Minimal:

[settings]
to = '[email protected]'

[[feeds]]
url = 'https://example.com/feed.xml'

[[feeds]]
url = 'https://example.org/feed.atom'

With default values:

error-report-to = [] # error-report-to = "[email protected]"

[settings]
to = []
cc = []
bcc = []
digest = false
max-mails-per-check = 5
item-subject = <src/templates/item-subject.txt>
digest-subject = <src/templates/digest-subject.txt>
item-body = <src/templates/item-body.html>
digest-body = <src/templates/digest-body.html>
template-args = {}
update-key = 'item.id'
interval = '1h'
keep-old = '1w'
timeout = '30s'
sanitize = true
sort-by-last-modified = false
http-headers = {}

[[feeds]]
url = "https://blog.rust-lang.org/feed.xml"
# urls = ["https://example.org/feed.atom", "https://example.net/feed.json"]
# To override [settings]:
# to = ["Alice <[email protected]>", "[email protected]"]
# cc = "[email protected]" is the same as cc = ["[email protected]"]
# bcc = []
# digest = true
# max-mails-per-check = 1
# item-subject.inline = "{{ item.title.content }}"
# digest-subject.inline = "My daily feed on {{ now() | dateformat(tz=template_args.tz) }}"
# item-body.file = "/path/to/item-template.html"
# digest-body.file = "/path/to/item-template.html"
# template-args.tz = "Asia/Shanghai"
# update-keys = ['item.title', 'item.content | capture("<main>([\\s\\S]*?)</main>", 1)']
# interval = '1d'
# keep-old = '2w'
# timeout = '1m'
# sanitize = false
# sort-by-last-modified = true
# http-headers.user-agent = "xxx"
feeds.filter.any = [
  { title-regex = '^Announcing' },
  {
    all = [
      { not.body-regex = 'foo' },
      { jinja-expr = 'item.author.name is match("John")' },
    ],
  },
]

Structure

  • Feeds are organized as groups ([[feeds]]). One group may contain one or more feed URLs. Feeds in the same group are combined together and items are deduplicated.
  • urls and filter are group-specific. Other settings may have a global default value in [settings]. Settings resolve in order: value on the feed group -> value in [settings] -> built-in default.

Fields

  • to, cc, bcc: Mail recipients. Each can be a single string or an array of strings.
  • digest: Whether to send all updates in a single digest mail or to send one mail per item. Newly added feeds and updates triggered by configuration changes (e.g. update-keys or filter) are always sent in digests.
  • max-mails-per-check: Send digest if there are too many updates, even if digest = false.
  • item-subject, digest-subject, item-body, digest-body: MiniJinja templates for mail contents.
    • Can be { inline = "{{ template }}" } or { file = "/path/to/template" }.
    • Default templates: src/templates.
    • Context for single item: { feed => Feed, item => Entry }, see feed_rs::model::Feed and feed_rs::model::Entry.
    • Context for digest: { feeds => [Feed], items => [{ feed => Feed, item => Entry }] }, where feeds are all feeds in the group (no matter updated or not), and items are updated items.
    • Custom args: template-args.
    • Can include each other, e.g. {% include "item-body.html" %}, {% include "digest-subject.txt" %}.
    • More features:
  • template-args: Custom args that are passed to the MiniJinja templates. Template args set on each feed are merged with the global setting. Args used by the default templates:
    • tz: timezone
    • group_title: used by the default digest-subject template to display the title for the entire feed group (useful when there are multiple URLs in a feed group)
  • update-keys/update-key: Keys that are used to check whether a feed item is updated or not. Each key is a MiniJinja expression. This can be used to control whether to notify feed content update.
  • interval: Check feed update once per interval.
  • keep-old: Prune old data in the database.
  • timeout: Timeout when fetching the feed.
  • sanitize: Whether to sanitize HTML in feed contents or keep the HTML as it is.
  • sort-by-last-modified: Whether to sort items in a digest by their last modified time.
  • http-headers: HTTP header map when fetching the feed.

  • url/urls: Feed URLs in the group.
  • filter: Filter feed items. Can be one of:
    • title-regex / body-regex / regex: Regular expression match for title / body / both.
    • jinja-expr: Evaluated as MiniJinja expression to see if it's true.
    • and: [..] (all: [..]) / or: [..] (any: [..]) / not: {..}: Logic combination.

  • error-report-to: Error report recipients when feeds are not working.

Security

  • Do not load untrusted config files. The config is designed to be flexible but insecure. Untrusted config may lead to SSTI, DoS attacks, and email bombs. This is out of the threat model for this project.
  • See Security for the security policy.

About

Send email alerts or digests when your RSS/Atom feeds update

Topics

Resources

License

Security policy

Stars

Watchers

Forks

Releases

No releases published

Sponsor this project

Packages

No packages published

Contributors 2

  •  
  •