Skip to content

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

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

EINDEX wants to merge 15 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.

CodeDok pushed a commit to CodeDok/mailrise that referenced this pull request Oct 5, 2026
Upgrade to Apprise v2.0.1+ and require Python 3.10+.

Security:
- Reply 535 to failed SMTP logins instead of leaving the client hanging (YoRyan#101)
- Compare passwords in constant time and hide whether a username exists
- Lock out IP addresses after repeated failed logins
- Warn when SMTP auth is enabled without TLS; add smtp.auth.require_tls
- Don't leak router exception details to SMTP clients
- Add smtp.data_size_limit and smtp.max_recipients
- Sanitize attachment filenames and keep attachment files in a private
  temporary directory; escape control characters in logged email fields
- Load the configuration with YAML's safe loader
- Docker: apply Debian security updates, drop pip, root-owned code (YoRyan#170)

Fixes and features:
- Fix crash on multipart/related HTML emails (YoRyan#135, YoRyan#171)
- Refuse unparseable messages instead of crashing
- Report non-Apprise notification exceptions as failures
- Add sender-based routing (YoRyan#145, YoRyan#157) and dotted usernames in config
  keys (YoRyan#164)
- Add PUID/PGID/TZ Docker build arguments (YoRyan#149)
- Fix bdist_wheel build error with recent setuptools (YoRyan#160)

CI: test on Python 3.10-3.14, scan the image with Trivy, rebuild weekly,
and publish images only to ghcr.io/codedok/mailrise.

BREAKING CHANGE: requires Python 3.10+ and Apprise 2; the Docker image runs
as UID/GID 1000 and is no longer published to Docker Hub.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
EINDEX and others added 2 commits October 22, 2026 15:41
The floating python:3 / python:3-slim tags now resolve to Python 3.15, so a
plain rebuild would silently jump the runtime from 3.14. Pin both stages to
3.14 on Debian 13 (trixie). A rebuild then ships ca-certificates
20250419+deb13u1 and the freshly resolved certifi 2026.10.19, both without the
leaked eMudhra intermediate CAs. Verified with a local build: requests rejects
the eMudhra probe hosts.
fix(docker): pin Python 3.14 on trixie for a fixed CA trust store
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