telegram-sendmail 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- telegram_sendmail-1.0.0/.gitignore +44 -0
- telegram_sendmail-1.0.0/CHANGELOG.md +109 -0
- telegram_sendmail-1.0.0/CONTRIBUTING.md +283 -0
- telegram_sendmail-1.0.0/LICENSE +21 -0
- telegram_sendmail-1.0.0/PKG-INFO +372 -0
- telegram_sendmail-1.0.0/README.md +334 -0
- telegram_sendmail-1.0.0/SECURITY.md +97 -0
- telegram_sendmail-1.0.0/docs/assets/banner-dark.png +0 -0
- telegram_sendmail-1.0.0/docs/assets/banner.png +0 -0
- telegram_sendmail-1.0.0/pyproject.toml +137 -0
- telegram_sendmail-1.0.0/src/telegram_sendmail/__init__.py +13 -0
- telegram_sendmail-1.0.0/src/telegram_sendmail/__main__.py +480 -0
- telegram_sendmail-1.0.0/src/telegram_sendmail/client.py +201 -0
- telegram_sendmail-1.0.0/src/telegram_sendmail/config.py +496 -0
- telegram_sendmail-1.0.0/src/telegram_sendmail/exceptions.py +86 -0
- telegram_sendmail-1.0.0/src/telegram_sendmail/parser.py +435 -0
- telegram_sendmail-1.0.0/src/telegram_sendmail/py.typed +1 -0
- telegram_sendmail-1.0.0/src/telegram_sendmail/smtp.py +380 -0
- telegram_sendmail-1.0.0/src/telegram_sendmail/spool.py +112 -0
- telegram_sendmail-1.0.0/telegram-sendmail.ini.example +90 -0
- telegram_sendmail-1.0.0/tests/__init__.py +1 -0
- telegram_sendmail-1.0.0/tests/conftest.py +483 -0
- telegram_sendmail-1.0.0/tests/test_client.py +401 -0
- telegram_sendmail-1.0.0/tests/test_config.py +689 -0
- telegram_sendmail-1.0.0/tests/test_main.py +930 -0
- telegram_sendmail-1.0.0/tests/test_parser.py +528 -0
- telegram_sendmail-1.0.0/tests/test_smtp.py +575 -0
- telegram_sendmail-1.0.0/tests/test_spool.py +336 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Build and distribution
|
|
2
|
+
dist/
|
|
3
|
+
build/
|
|
4
|
+
*.egg-info/
|
|
5
|
+
*.egg
|
|
6
|
+
|
|
7
|
+
# PyInstaller / Nuitka binary artifacts
|
|
8
|
+
*.spec
|
|
9
|
+
*.onefile-build/
|
|
10
|
+
__pycache__/
|
|
11
|
+
*.py[cod]
|
|
12
|
+
*$py.class
|
|
13
|
+
|
|
14
|
+
# Virtual environments
|
|
15
|
+
.venv/
|
|
16
|
+
venv/
|
|
17
|
+
env/
|
|
18
|
+
ENV/
|
|
19
|
+
|
|
20
|
+
# Testing and coverage
|
|
21
|
+
.pytest_cache/
|
|
22
|
+
.coverage
|
|
23
|
+
coverage.json
|
|
24
|
+
coverage.xml
|
|
25
|
+
htmlcov/
|
|
26
|
+
|
|
27
|
+
# Quality tools cache
|
|
28
|
+
.mypy_cache/
|
|
29
|
+
.ruff_cache/
|
|
30
|
+
|
|
31
|
+
# Configuration files with secrets
|
|
32
|
+
*.ini
|
|
33
|
+
|
|
34
|
+
# Editor and OS artifacts
|
|
35
|
+
.idea/
|
|
36
|
+
.vscode/
|
|
37
|
+
*.swp
|
|
38
|
+
*.swo
|
|
39
|
+
.DS_Store
|
|
40
|
+
Thumbs.db
|
|
41
|
+
|
|
42
|
+
# Logs and spools
|
|
43
|
+
*.log
|
|
44
|
+
/var/
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [1.0.0] - 2026-03-08
|
|
11
|
+
|
|
12
|
+
Production release.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
#### Drop-in sendmail compatibility
|
|
17
|
+
|
|
18
|
+
- Accepts all flags used by system daemons in practice: `-f`/`-r` (envelope
|
|
19
|
+
sender), `-s` (subject), `-bs` (SMTP server mode), `-t`, `-i`, `-oi`.
|
|
20
|
+
Positional recipient arguments (e.g. `sendmail root@localhost`) are consumed
|
|
21
|
+
silently; all mail is forwarded to the configured Telegram `chat_id`.
|
|
22
|
+
- **Pipe mode**: reads a raw RFC 2822 email from `stdin`, the standard
|
|
23
|
+
interface used by cron, logwatch, fail2ban, and the majority of Unix system
|
|
24
|
+
daemons.
|
|
25
|
+
- **SMTP server mode** (`-bs`): runs a minimal SMTP dialogue on
|
|
26
|
+
`stdin`/`stdout` without opening a network socket. Supported commands:
|
|
27
|
+
`EHLO`, `HELO`, `MAIL FROM`, `RCPT TO`, `DATA`, `RSET`, `NOOP`, `QUIT`.
|
|
28
|
+
Unrecognised commands return `250 Ok` for maximum daemon compatibility.
|
|
29
|
+
RFC 5321 dot-stuffing and null reverse-path (`MAIL FROM:<>`) are handled
|
|
30
|
+
correctly. A failed delivery attempt returns `554 Transaction failed` and
|
|
31
|
+
resets the session state; subsequent messages in the same session are
|
|
32
|
+
unaffected.
|
|
33
|
+
|
|
34
|
+
#### Zero-data-loss mail spooling
|
|
35
|
+
|
|
36
|
+
- Every raw email is written to a local spool file **before** any Telegram API
|
|
37
|
+
call is attempted. If the network is unavailable or delivery fails, the
|
|
38
|
+
original message is preserved on disk.
|
|
39
|
+
- The spool path defaults to `/var/mail/<username>`. If that directory is not
|
|
40
|
+
writable — common in containers — the daemon falls back to
|
|
41
|
+
`/tmp/.telegram-sendmail-spool/<username>` and emits a `WARNING` to syslog.
|
|
42
|
+
This hidden subdirectory is created on demand with `0700` DAC permissions,
|
|
43
|
+
preventing cross-user data exposure in the world-writable `/tmp` filesystem.
|
|
44
|
+
- A spool write failure is non-fatal: the message is still forwarded to
|
|
45
|
+
Telegram and the failure is surfaced in syslog at `WARNING` level.
|
|
46
|
+
- File permissions on the spool file are enforced to `0600` via `os.fchmod`
|
|
47
|
+
against the open file descriptor on every write, eliminating the TOCTOU race
|
|
48
|
+
condition that would otherwise exist in world-writable directories.
|
|
49
|
+
|
|
50
|
+
#### Resilient Telegram delivery
|
|
51
|
+
|
|
52
|
+
- Retriable responses (HTTP 429 and 5xx) cause the process to exit with
|
|
53
|
+
code `75` (`EX_TEMPFAIL`), signalling MTA-aware daemons to re-queue rather
|
|
54
|
+
than treat the message as permanently undeliverable.
|
|
55
|
+
- HTTP retries with exponential backoff on `429 Too Many Requests` and `5xx`
|
|
56
|
+
server errors. Retry count and backoff multiplier are operator-configurable.
|
|
57
|
+
- The Telegram API's `ok` field in the JSON response body is validated
|
|
58
|
+
independently of the HTTP status code, since the API can return HTTP 200
|
|
59
|
+
with `ok: false` for application-level errors such as an invalid `chat_id`.
|
|
60
|
+
|
|
61
|
+
#### HTML email support
|
|
62
|
+
|
|
63
|
+
- A two-pass sanitisation pipeline strips dangerous elements — including
|
|
64
|
+
`<script>`, `<style>`, `<iframe>`, `<object>`, `<embed>`, `<svg>`, and
|
|
65
|
+
`<noscript>` — and their entire subtrees before further processing.
|
|
66
|
+
Arbitrarily nested dangerous tags are handled correctly via depth tracking.
|
|
67
|
+
- Safe markup is converted to Telegram's supported HTML subset: `<b>`, `<i>`,
|
|
68
|
+
`<u>`, `<s>`, `<a>`, `<code>`, `<pre>`, `<blockquote>`. `javascript:` hrefs
|
|
69
|
+
are silently discarded.
|
|
70
|
+
- Plain-text parts are preferred; HTML is used as a fallback when no
|
|
71
|
+
`text/plain` part is present in the MIME structure.
|
|
72
|
+
- Multipart attachments are detected and a notice is appended to the Telegram
|
|
73
|
+
message. Attachment filenames are never disclosed.
|
|
74
|
+
- Long bodies are truncated at the nearest word boundary below the configured
|
|
75
|
+
limit; a visible truncation notice is appended.
|
|
76
|
+
|
|
77
|
+
#### Secure configuration
|
|
78
|
+
|
|
79
|
+
- Configuration is loaded from `/etc/telegram-sendmail.ini` (system-wide) or
|
|
80
|
+
`~/.telegram-sendmail.ini` (per-user). The user file takes precedence.
|
|
81
|
+
- Config files with group or world read bits emit a `WARNING` to syslog on
|
|
82
|
+
every startup. The check is advisory and does not block execution.
|
|
83
|
+
- Malformed or out-of-range values in `[options]` fall back to their defaults
|
|
84
|
+
with a `WARNING` logged; a single bad option never blocks delivery.
|
|
85
|
+
- Available options: `spool_dir`, `message_max_length`, `smtp_timeout`,
|
|
86
|
+
`telegram_timeout`, `max_retries`, `backoff_factor`, `disable_notification`.
|
|
87
|
+
- A fully-commented configuration template is provided as
|
|
88
|
+
`telegram-sendmail.ini.example`.
|
|
89
|
+
|
|
90
|
+
#### Structured logging
|
|
91
|
+
|
|
92
|
+
- All output goes to syslog under the `LOG_MAIL` facility with the identifier
|
|
93
|
+
`telegram-sendmail`, queryable via `journalctl -t telegram-sendmail`.
|
|
94
|
+
- `--console` flag routes log output to `stderr` for interactive debugging
|
|
95
|
+
and CI environments.
|
|
96
|
+
- `--debug` flag sets the log level to `DEBUG`, exposing SMTP command traces
|
|
97
|
+
and API request details.
|
|
98
|
+
|
|
99
|
+
#### Exit codes
|
|
100
|
+
|
|
101
|
+
| Code | Meaning |
|
|
102
|
+
|------|---------------------------------------------------------------|
|
|
103
|
+
| `0` | Message delivered successfully. |
|
|
104
|
+
| `1` | Operational failure (parse error, unrecoverable API error). |
|
|
105
|
+
| `75` | Transient failure — retriable error; daemon should retry. |
|
|
106
|
+
| `78` | Configuration error (missing file or missing required key). |
|
|
107
|
+
|
|
108
|
+
[Unreleased]: https://github.com/theodiv/telegram-sendmail/compare/v1.0.0...HEAD
|
|
109
|
+
[1.0.0]: https://github.com/theodiv/telegram-sendmail/releases/tag/v1.0.0
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# Contributing to telegram-sendmail
|
|
2
|
+
|
|
3
|
+
Contributions are welcome. This document defines the standards, workflow, and
|
|
4
|
+
expectations for all submissions to this project.
|
|
5
|
+
|
|
6
|
+
## Table of Contents
|
|
7
|
+
|
|
8
|
+
- [Code of Conduct](#code-of-conduct)
|
|
9
|
+
- [Getting Started](#getting-started)
|
|
10
|
+
- [Development Environment](#development-environment)
|
|
11
|
+
- [Toolchain & Quality Standards](#toolchain--quality-standards)
|
|
12
|
+
- [Running Tests](#running-tests)
|
|
13
|
+
- [Pull Request Process](#pull-request-process)
|
|
14
|
+
- [Reporting Issues](#reporting-issues)
|
|
15
|
+
- [Commit Message Convention](#commit-message-convention)
|
|
16
|
+
|
|
17
|
+
## Code of Conduct
|
|
18
|
+
|
|
19
|
+
All interactions must remain professional and respectful. Harassment,
|
|
20
|
+
dismissive language, or personal attacks of any kind are not tolerated. Conduct
|
|
21
|
+
concerns may be raised via a private issue or by contacting the maintainer
|
|
22
|
+
directly through the contact information on the GitHub profile.
|
|
23
|
+
|
|
24
|
+
## Getting Started
|
|
25
|
+
|
|
26
|
+
**Prerequisites:** Python 3.10 or later, `git`, and `pip`.
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# 1. Fork the repository on GitHub, then clone the fork
|
|
30
|
+
git clone https://github.com/<your-username>/telegram-sendmail.git
|
|
31
|
+
cd telegram-sendmail
|
|
32
|
+
|
|
33
|
+
# 2. Create an isolated virtual environment
|
|
34
|
+
python3 -m venv .venv
|
|
35
|
+
source .venv/bin/activate
|
|
36
|
+
|
|
37
|
+
# 3. Install the package in editable mode with all dev dependencies
|
|
38
|
+
pip install -e ".[dev]"
|
|
39
|
+
|
|
40
|
+
# 4. Install and activate the pre-commit hooks
|
|
41
|
+
pre-commit install
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Step 3 installs the package from the `src/` layout in editable mode so changes
|
|
45
|
+
to `src/telegram_sendmail/` are immediately reflected without reinstalling. The
|
|
46
|
+
`[dev]` extra installs the complete quality toolchain described below.
|
|
47
|
+
|
|
48
|
+
After this setup, every `git commit` will automatically run the full quality
|
|
49
|
+
pipeline on staged files via pre-commit.
|
|
50
|
+
|
|
51
|
+
## Development Environment
|
|
52
|
+
|
|
53
|
+
The project uses the standard `src/` layout. Source files reside under
|
|
54
|
+
`src/telegram_sendmail/`. Tests reside under `tests/`. Source files must not
|
|
55
|
+
be placed outside the `src/` tree.
|
|
56
|
+
|
|
57
|
+
Key conventions:
|
|
58
|
+
|
|
59
|
+
- `src/telegram_sendmail/py.typed` is a PEP 561 marker file indicating that
|
|
60
|
+
this package ships inline type annotations.
|
|
61
|
+
- The authoritative version string is defined once in
|
|
62
|
+
`src/telegram_sendmail/__init__.py` (`__version__`). Hatchling reads it from
|
|
63
|
+
there. The version string must not be duplicated anywhere else in the
|
|
64
|
+
codebase.
|
|
65
|
+
- Configuration for all tools lives exclusively in `pyproject.toml`. Additional
|
|
66
|
+
tool-specific config files (`setup.cfg`, `tox.ini`, `.flake8`, etc.) are not
|
|
67
|
+
permitted.
|
|
68
|
+
|
|
69
|
+
The `[dev]` dependency group installs everything required for development:
|
|
70
|
+
|
|
71
|
+
| Tool | Purpose |
|
|
72
|
+
|-----------------|-----------------------------------------|
|
|
73
|
+
| `ruff` | Linting, formatting, and import sorting |
|
|
74
|
+
| `mypy` | Static type checking (strict mode) |
|
|
75
|
+
| `pytest` | Test runner |
|
|
76
|
+
| `pytest-cov` | Coverage measurement |
|
|
77
|
+
| `requests-mock` | HTTP transport-layer mocking |
|
|
78
|
+
| `pre-commit` | Git hook manager |
|
|
79
|
+
|
|
80
|
+
### Binary builds — PyInstaller
|
|
81
|
+
|
|
82
|
+
The release pipeline compiles the package into a standalone binary using
|
|
83
|
+
[PyInstaller](https://pyinstaller.org/). Contributors working on imports,
|
|
84
|
+
entry points, or packaging must be aware of the following constraints:
|
|
85
|
+
|
|
86
|
+
- **Hidden imports:** modules imported dynamically (e.g. via
|
|
87
|
+
`importlib.import_module`) rather than through a static `import` statement
|
|
88
|
+
are not detected by PyInstaller's dependency analyser. A `--hidden-import`
|
|
89
|
+
flag or a `.spec` file hook is required for any such import.
|
|
90
|
+
- **Data files:** non-Python assets referenced at runtime (e.g. via
|
|
91
|
+
`importlib.resources` or path construction relative to `__file__`) must be
|
|
92
|
+
declared explicitly in the PyInstaller spec or they will not be bundled. The
|
|
93
|
+
current codebase has no such assets, but contributors adding them must
|
|
94
|
+
account for this.
|
|
95
|
+
- **`__file__` assumptions:** inside a frozen binary, `__file__` does not
|
|
96
|
+
point to a `.py` source file on disk. Any runtime path construction that
|
|
97
|
+
relies on `__file__` will behave differently inside the compiled binary
|
|
98
|
+
versus a standard Python installation. Use `importlib.resources` for
|
|
99
|
+
accessing package data instead.
|
|
100
|
+
- **Smoke-testing:** the release CI runs `telegram-sendmail --version` against
|
|
101
|
+
the compiled binary as a basic import sanity check. A PR that introduces an
|
|
102
|
+
import or packaging change that causes the binary to fail at startup will be
|
|
103
|
+
caught at this step.
|
|
104
|
+
|
|
105
|
+
To verify a local binary build before opening a PR that affects imports or
|
|
106
|
+
packaging:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
pip install pyinstaller
|
|
110
|
+
pyinstaller --onefile --name telegram-sendmail --strip \
|
|
111
|
+
src/telegram_sendmail/__main__.py
|
|
112
|
+
./dist/telegram-sendmail --version
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Toolchain & Quality Standards
|
|
116
|
+
|
|
117
|
+
All toolchain configuration lives in `pyproject.toml`. The pre-commit pipeline
|
|
118
|
+
enforces these checks on every commit; CI enforces them on every push. A
|
|
119
|
+
contribution will not be merged if any check fails.
|
|
120
|
+
|
|
121
|
+
### Linting & Formatting — Ruff
|
|
122
|
+
|
|
123
|
+
Ruff is the sole tool for formatting, linting, and import sorting.
|
|
124
|
+
The `pyproject.toml` configuration enables rule sets
|
|
125
|
+
including `pyflakes`, `pycodestyle`, `pyupgrade`, `pylint`, and
|
|
126
|
+
`flake8-pytest-style`.
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
# Check for errors and auto-fix what is safe to fix automatically
|
|
130
|
+
ruff check --fix .
|
|
131
|
+
|
|
132
|
+
# Format all source files
|
|
133
|
+
ruff format .
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
The pre-commit hook runs both commands automatically on staged files.
|
|
137
|
+
|
|
138
|
+
### Type Checking — MyPy (Strict Mode)
|
|
139
|
+
|
|
140
|
+
All new code must pass MyPy under `strict` mode:
|
|
141
|
+
|
|
142
|
+
- Every function and method requires complete type annotations on parameters
|
|
143
|
+
and return values.
|
|
144
|
+
- `Any` is disallowed unless no viable alternative exists. If used, a comment
|
|
145
|
+
explaining the necessity is mandatory.
|
|
146
|
+
- `# type: ignore` comments are forbidden without a specific error code and a
|
|
147
|
+
documented rationale (e.g.,
|
|
148
|
+
`# type: ignore[misc] # html2text callback signature is untyped upstream`).
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
mypy src/ tests/
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Running All Checks at Once
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
pre-commit run --all-files
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Running Tests
|
|
161
|
+
|
|
162
|
+
Tests use `pytest` and reside in the `tests/` directory. Shared fixtures are
|
|
163
|
+
provided in `tests/conftest.py`. No test may make live network calls or write
|
|
164
|
+
to real filesystem paths outside `pytest`'s `tmp_path`.
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
# Full test suite with coverage report
|
|
168
|
+
pytest
|
|
169
|
+
|
|
170
|
+
# Single module
|
|
171
|
+
pytest tests/test_parser.py
|
|
172
|
+
|
|
173
|
+
# Verbose output
|
|
174
|
+
pytest -v
|
|
175
|
+
|
|
176
|
+
# Keyword filter
|
|
177
|
+
pytest -k "smtp"
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
The global coverage gate is set at **80%**. Critical logic in `client.py`,
|
|
181
|
+
`parser.py`, and `smtp.py` is enforced at **90%** by CI. New code should
|
|
182
|
+
meet or exceed these thresholds.
|
|
183
|
+
|
|
184
|
+
### Test design standards
|
|
185
|
+
|
|
186
|
+
- Tests must exercise **real behaviour**, not mirror the implementation's
|
|
187
|
+
assumptions. A mock or fixture that encodes the same incorrect assumption
|
|
188
|
+
as the source code is a tautological test — it provides false confidence.
|
|
189
|
+
- Tests for filesystem operations (spool writes, config file reads) must use
|
|
190
|
+
`tmp_path` and must be skipped on UID 0 where they depend on DAC permission
|
|
191
|
+
enforcement, because root bypasses file permission checks on Linux.
|
|
192
|
+
- `requests-mock` is the standard mechanism for mocking HTTP calls. It patches
|
|
193
|
+
at the transport layer so the full `requests.Session` → `HTTPAdapter`
|
|
194
|
+
pipeline runs, except for the actual network call.
|
|
195
|
+
- Fixtures in `conftest.py` must be genuinely shared across multiple test
|
|
196
|
+
modules. Single-use fixtures — those consumed by only one test function or
|
|
197
|
+
one test class — must be defined locally in the relevant test module or as
|
|
198
|
+
a local helper within the test itself. Accumulating single-use fixtures in
|
|
199
|
+
`conftest.py` inflates the shared namespace, increases fixture discovery
|
|
200
|
+
overhead, and makes the file harder to reason about for contributors
|
|
201
|
+
unfamiliar with the codebase.
|
|
202
|
+
|
|
203
|
+
## Pull Request Process
|
|
204
|
+
|
|
205
|
+
1. **Branch from `main`** using a descriptive name:
|
|
206
|
+
`feat/retry-on-telegram-timeout`, `fix/smtp-dot-stuffing-edge-case`.
|
|
207
|
+
|
|
208
|
+
2. **Keep PRs focused.** One logical change per PR. Unrelated improvements
|
|
209
|
+
observed while working on a fix should be submitted as separate PRs.
|
|
210
|
+
|
|
211
|
+
3. **Update `CHANGELOG.md`** under the `[Unreleased]` section following the
|
|
212
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) format.
|
|
213
|
+
|
|
214
|
+
4. **Ensure the full pre-commit pipeline passes** before opening the PR:
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
pre-commit run --all-files
|
|
218
|
+
pytest
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
5. **Write or update tests** for any logic added or modified.
|
|
222
|
+
|
|
223
|
+
6. **Complete the PR description** — explain *what* changed and *why*. Link
|
|
224
|
+
to the relevant issue if one exists.
|
|
225
|
+
|
|
226
|
+
7. A PR requires **one approval** from the maintainer before merging. Requested
|
|
227
|
+
changes should be addressed in new commits rather than force-pushes so the
|
|
228
|
+
review history is preserved.
|
|
229
|
+
|
|
230
|
+
8. **Squash-merging** is the preferred strategy to maintain a linear `main` branch
|
|
231
|
+
history.
|
|
232
|
+
|
|
233
|
+
## Reporting Issues
|
|
234
|
+
|
|
235
|
+
Before opening an issue, check the existing
|
|
236
|
+
[Issues](https://github.com/theodiv/telegram-sendmail/issues) to avoid
|
|
237
|
+
duplicates, and inspect `journalctl -t telegram-sendmail -f` for relevant
|
|
238
|
+
log output.
|
|
239
|
+
|
|
240
|
+
When opening an issue, include:
|
|
241
|
+
|
|
242
|
+
- The version (`telegram-sendmail --version`)
|
|
243
|
+
- Python version (`python3 --version`) and Linux distribution
|
|
244
|
+
- Installation method (pre-built binary or source)
|
|
245
|
+
- Whether the issue manifests in pipe mode or SMTP mode (`-bs`)
|
|
246
|
+
- Sanitised log output (bot token and chat ID must be removed before posting)
|
|
247
|
+
- Steps to reproduce
|
|
248
|
+
|
|
249
|
+
## Commit Message Convention
|
|
250
|
+
|
|
251
|
+
This project follows the
|
|
252
|
+
[Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/)
|
|
253
|
+
specification. Compliance is enforced automatically on every pull request.
|
|
254
|
+
|
|
255
|
+
```
|
|
256
|
+
<type>(<scope>): <short summary>
|
|
257
|
+
|
|
258
|
+
[optional body]
|
|
259
|
+
|
|
260
|
+
[optional footer: Closes #<issue>]
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
**Types:** `feat`, `fix`, `refactor`, `test`, `docs`, `chore`, `perf`, `ci`.
|
|
264
|
+
|
|
265
|
+
**Scope** (optional): `config`, `parser`, `client`, `smtp`, `spool`, `cli`,
|
|
266
|
+
`build`.
|
|
267
|
+
|
|
268
|
+
**Examples:**
|
|
269
|
+
|
|
270
|
+
```
|
|
271
|
+
feat(client): add exponential backoff retry on Telegram 429 responses
|
|
272
|
+
|
|
273
|
+
fix(smtp): handle bare LF in DATA stream without panicking
|
|
274
|
+
|
|
275
|
+
docs: document update-alternatives symlink strategy in README
|
|
276
|
+
|
|
277
|
+
build: add hidden-import hook for dynamic logging backend
|
|
278
|
+
|
|
279
|
+
chore: bump pre-commit hook revisions
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
The short summary must use the imperative mood ("add", "fix", "remove") and
|
|
283
|
+
must not end with a period.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Theodosios Divolis
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|