Skip to content

feat: Add sender-based routing support with backward compatibility (Fixes #145)#157

Open
EINDEX wants to merge 13 commits into
YoRyan:mainfrom
EINDEX:main
Open

feat: Add sender-based routing support with backward compatibility (Fixes #145)#157
EINDEX wants to merge 13 commits into
YoRyan:mainfrom
EINDEX:main

Conversation

@EINDEX

@EINDEX EINDEX commented Jan 7, 2026

Copy link
Copy Markdown

Summary

Adds support for routing notifications by sender address in addition to recipient address.

Backward compatible: existing configs.<recipient> direct configurations keep their recipient-based meaning.

Resolves #145

Changes

  • Enhanced SimpleRouter to match sender and recipient patterns using fnmatch
  • Keeps direct configs as recipient -> config with an implicit *@* sender
  • Adds nested configs as sender -> recipient -> config for sender-scoped routing
  • Fixes multi-recipient handling in the simple router
  • Documents sender-scoped routing examples

Examples

Sender-scoped routing for any recipient:

configs:
  "unifi@domain.com":
    "*@*":
      urls:
        - pover://USER_KEY@TOKEN

Fine-grained sender and recipient routing:

configs:
  "monitoring@*":
    "alerts@mycompany.com":
      urls:
        - slack://...

Testing

  • /Users/eindex/.pyenv/versions/3.12.3/bin/python -m pytest -q

Follow-up

Dotted full email config keys are handled in stacked PR EINDEX#1 for #164.

EINDEX and others added 11 commits June 28, 2025 20:22
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@v3...v4)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](actions/checkout@v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
…gurations

- Modified the senders attribute to include receiver keys.
- Enhanced the load_from_yaml function to handle both direct and nested configurations.
- Updated tests to reflect changes in sender and receiver key handling.

Signed-off-by: EINDEX <snowstarlbk@gmail.com>
@EINDEX EINDEX mentioned this pull request Jan 7, 2026
@ZzenlD

ZzenlD commented Mar 24, 2026

Copy link
Copy Markdown

Hey @EINDEX, I opened issue #164 because the recipient email address doesn't allow periods (e.g., foo.bar@example.com).

If you could modify the two functions below as follows, this would be possible while preserving all existing functionality:

class Sent2RecvRouter(Router):
    [...]
    async def email_to_apprise(
        self, logger: Logger, email: EmailMessage, auth_data: typ.Any, **kwargs) \
            -> typ.AsyncGenerator[AppriseNotification, None]:
        [...]
        mapping = {
            'subject': email.subject,
            'from': email.from_,
            'from_name': email.from_.partition('<')[0].strip(),
            'body': email.body,
            'to': str(rcpt.key),
            'config': rcpt.key.as_configured(),
            'type': rcpt.notify_type
        }

def _parse_sent2recv_key(logger: Logger, key: str) -> _Key:
    def fatal():
        logger.critical(
            "Invalid config key '%s'; should be a string or an email address",
            key)
        raise SystemExit(1)
    if '@' in key:
        user, domain = _parseaddrparts(key)
        if not user or not domain:
            fatal()
        return _Key(user=user, domain=domain.lower())
    if '.' in key:
        fatal()

    return _Key(user=key)

Maybe you could include this in your pull request as well 😄

Thanks for your great work.

@ZzenlD

ZzenlD commented May 26, 2026

Copy link
Copy Markdown

Hey, sorry it took me a while to test this.
Thanks for the adjustment—it works great. I have one more “request”:

Could you add the single line 'from_name': email.from_.partition('<')[0].strip(),, as shown in my last code example:

class Sent2RecvRouter(Router):
    [...]
    async def email_to_apprise(
        self, logger: Logger, email: EmailMessage, auth_data: typ.Any, **kwargs) \
            -> typ.AsyncGenerator[AppriseNotification, None]:
        [...]
        mapping = {
            'subject': email.subject,
            'from': email.from_,
            'from_name': email.from_.partition('<')[0].strip(),
            'body': email.body,
            'to': str(rcpt.key),
            'config': rcpt.key.as_configured(),
            'type': rcpt.notify_type
        }

This allows the from_name variable to be accessed in the template. Thanks :)

Let’s hope @YoRyan picks up on these changes soon, including an Apprise update.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Routing by sender

3 participants