62 lines
3.6 KiB
Markdown
62 lines
3.6 KiB
Markdown
# email_tunnel
|
|
|
|
Poll Proton Bridge via IMAP and inject messages into a local SMTP server (e.g., Haraka). The script is designed for headless, continuous operation with basic safety/observability baked in.
|
|
|
|
## What it does
|
|
- Connects to Proton Bridge IMAP via STARTTLS and selects a required folder.
|
|
- Polls for new messages by UID, fetches each, and relays it verbatim to a local SMTP server using the original headers.
|
|
- Strips `Bcc`, deduplicates recipients, and skips malformed addresses.
|
|
- Tracks progress in a JSON state file so messages are processed once, even across restarts.
|
|
- Retries IMAP fetch/search and SMTP delivery with bounded attempts/backoff; advances state past permanent delivery failures to avoid loops.
|
|
- Enforces single-instance execution via a pidfile lock.
|
|
- Emits scrape-friendly INFO logs each cycle with counts, last UID, and cycle duration.
|
|
|
|
## Requirements
|
|
- Python 3.x
|
|
- Proton Bridge IMAP reachable at the configured host/port
|
|
- Local SMTP server reachable (e.g., Haraka bound to a non-25 port)
|
|
|
|
## Configuration (environment variables)
|
|
- `IMAP_IP` (required): Proton Bridge IMAP host.
|
|
- `IMAP_PORT` (required): Proton Bridge IMAP port (typically 1143).
|
|
- `IMAP_USER` (required): IMAP username.
|
|
- `IMAP_PASSWORD` (required): IMAP password.
|
|
- `IMAP_FOLDER` (required): IMAP folder to poll; must exist (no fallback).
|
|
- `IMAP_TLS_VERIFY` (default `1`): Set to `0`/`false` to disable cert/hostname checks (dev only).
|
|
- `SMTP_IP` (default `127.0.0.1`): SMTP host to deliver to.
|
|
- `SMTP_PORT` (default `2525`): SMTP port to deliver to.
|
|
- `POLL_INTERVAL` (default `15`): Seconds between mailbox polls.
|
|
- `STATE_FILE` (default `./state.json`): Path to persist last processed UID.
|
|
- `PIDFILE` (default `/tmp/email_tunnel.pid`): Pidfile for single-instance lock.
|
|
- `LOG_LEVEL` (default `INFO`): Standard logging levels (`DEBUG`, `INFO`, etc.).
|
|
|
|
## Usage
|
|
```bash
|
|
export IMAP_IP=127.0.0.1
|
|
export IMAP_PORT=1143
|
|
export IMAP_USER=...
|
|
export IMAP_PASSWORD=...
|
|
export IMAP_FOLDER=loomio
|
|
export SMTP_IP=127.0.0.1
|
|
export SMTP_PORT=2525
|
|
python3 email_tunnel.py
|
|
```
|
|
|
|
Run under a process supervisor (systemd, supervisord, etc.) so restarts and log rotation are handled.
|
|
|
|
## Operational behavior
|
|
- State tracking: `STATE_FILE` records the last successfully handled UID. It is only advanced after successful delivery, or after a logged `DeliveryError` to avoid reprocessing a permanently failed message.
|
|
- Retries: IMAP SEARCH/FETCH and SMTP SEND are retried up to 3 times with small backoff. Persistent failures log and move on.
|
|
- Single instance: pidfile lock prevents multiple concurrent runs; stale pidfiles are replaced if the recorded PID is not running.
|
|
- Metrics logging: every poll cycle emits a single INFO line with `metrics` prefix and key/value pairs (`cycle_processed`, `cycle_delivered`, `cycle_failed`, `session_*`, `last_uid`, `cycle_duration_ms`).
|
|
- Missing state: if `STATE_FILE` is absent on startup, the script initializes it to the current max UID (or 0 for an empty mailbox) to avoid reprocessing the entire mailbox.
|
|
|
|
## Notes and safety
|
|
- Leave `IMAP_TLS_VERIFY` enabled in production; disable only for trusted, local endpoints.
|
|
- Ensure the `STATE_FILE` and `PIDFILE` paths are writable by the service user.
|
|
- If delivering to a server that rejects certain recipients, the script will log the error, advance the UID, and continue.
|
|
|
|
## Troubleshooting
|
|
- Increase verbosity with `LOG_LEVEL=DEBUG` to see raw message dumps and IMAP folder listings.
|
|
- If the script exits immediately, verify required env vars and that `IMAP_FOLDER` exists on the server.
|
|
- For pidfile conflicts, check the process holding the PID or remove a stale pidfile if no process is running.***
|