Skip to main content

Send personalised welcome emails to guests loaded from iCal calendar URLs

Project description

welcomer

logo

Sends configurable welcome emails to guests loaded from iCal calendar URLs. Built for accommodation businesses — reads reservations from a calendar and emails each guest a personalised welcome message.

Install

pip install welcomer
# or with uv (recommended)
uv tool install welcomer
# or with pipx
pipx install welcomer

Container

podman run --rm \
  -v ~/.config/welcomer:/root/.config/welcomer:ro \
  ghcr.io/pdostal/welcomer --dry-run

latest is updated on every push to master. Tagged releases follow vX.Y.Z.

Setup

brew install uv taplo
uv sync
# copy the example config below to config.toml and edit it

Usage

uv run welcomer --dry-run              # preview recipients list
uv run welcomer --dry-run --print-note # also show rendered message per guest
uv run welcomer                        # interactive send (default)
uv run welcomer --yes                  # send to all without prompting
uv run welcomer --yes --silent         # send silently (suppress info messages)
uv run welcomer --dry-run --test-config  # test with bundled sample calendars

Config

Config is loaded from the first path that exists:

  1. config.toml in the current directory
  2. ~/.config/welcomer/config.toml

Example config:

subject = "Reservation confirmed – {{ name }}"

# Only show reservations starting within this many days from today (optional)
# days = 30

# Days before check-in when a reservation becomes eligible to send (default: 14)
# advance = 14

# Send to CC/BCC even when the guest email is unknown (default: false)
# send_without_email = false

body = """
Dear {{ name }},

Thank you for your reservation from {{ start }} to {{ end }} at {{ official_name }}.
{% if adults %}We look forward to hosting you ({{ adults }} adults{% if kids %}, {{ kids }} kids{% endif %}).
{% endif %}
If you have any questions, reply to this email.
"""

[smtp]
host = "smtp.example.com"
port = 587
from = "info@myproperty.com"
from_name = "My Property"          # optional: sets the From display name
# cc = ["manager@myproperty.com"] # optional: CC every outgoing email
# bcc = ["audit@myproperty.com"]  # optional: BCC (not shown in headers)
username = "info@myproperty.com"
password = "secret"
tls = true      # use STARTTLS (port 587)
# ssl = true    # use SSL/TLS instead (port 465)

[[calendars]]
property = "My Property"
official_name = "My Property s.r.o."  # optional: legal/official name for templates
provider = "BookingProvider"
url = "https://example.com/calendar.ics"

Backward compatibility: the name key in [[calendars]] is still accepted and maps to property. Existing configs do not need to be updated.

Template variables

Templates use Jinja2 syntax: {{ variable }}, {% if condition %}...{% endif %}, filters (| upper, | default('fallback', true)).

| Variable | Description | Empty when | | -------------- | -------------------------------------------- | -------------------------- | | name | Guest name | — | | email | Guest email address | no email on reservation | | phone | Guest phone number | no phone on reservation | | start | Check-in date (formatted per date_format) | — | | end | Check-out date (formatted per date_format) | — | | adults | Number of adult guests | not provided by calendar | | kids | Number of child guests | not provided by calendar | | property | Property name (from [[calendars]]) | not configured | | official_name | Legal/official property name | falls back to property | | provider | Booking provider name | not configured | | summary | Raw iCal event summary | — |

Jinja2 examples:

{# Conditional block #}
{% if phone %}We can be reached at {{ phone }}.{% endif %}

{# Fallback value #}
{{ phone | default('contact us by email', true) }}

{# Inline conditional #}
{{ phone if phone else 'reply to this email' }}

{# Both adults and kids with nested condition #}
{% if adults %}{{ adults }} adults{% if kids %}, {{ kids }} kids{% endif %}{% endif %}

{# Upper-case filter #}
{{ name | upper }}

Local SMTP testing with Mailpit

Mailpit catches outgoing emails locally without actually delivering them. It gives you a web inbox at http://localhost:8025 and listens for SMTP on port 1025 — no account, no real delivery, no risk of accidentally mailing guests.

brew install mailpit
mailpit                  # starts in the foreground; Ctrl-C to stop

Mailpit also runs as a macOS service if you prefer:

brew services start mailpit

Add this to your config while testing:

[smtp]
host = "localhost"
port = 1025
from = "test@localhost"
# no username / password / tls needed for Mailpit

Then run welcomer as normal — all emails land in the Mailpit inbox at http://localhost:8025 instead of being delivered.

Interactive mode

Interactive mode is on by default. The app prompts before sending each eligible email (check-in within the advance window, default 14 days). Use --yes to skip prompts. Previously sent reservations are tracked in ~/.config/welcomer/sent.log and skipped automatically on future runs.

The Sent column shows the status of each reservation:

| Symbol | Colour | Meaning | | ------ | ------ | ------- | | | green | Already sent (in sent.log) | | | green | Eligible to send now (check-in within advance window) | | | yellow | Not yet eligible (check-in too far away) | | (empty) | — | No email address (unless send_without_email = true), or check-in already passed and not yet sent |

When send_without_email = true, reservations without a guest email still show / and will be sent to CC/BCC recipients only. Requires CC or BCC to be configured in [smtp].

Calendar cache

Remote iCal URLs are cached for 5 hours in ~/.config/welcomer/cache/. This avoids hitting your calendar provider on every run. Use --force-refresh to bypass the cache and re-fetch all remote URLs immediately:

uv run welcomer --dry-run --force-refresh

The cache directory is created automatically. Each URL is stored as a separate <sha256-of-url>.ics file. Local file paths and --test-config are never cached.

Multi-property reservations

When the same guest (identical name, provider, check-in, and check-out) appears across multiple properties, welcomer merges them into a single Multi entry and sends one email. The property column shows Multi and overlapping reservations from other providers are still detected correctly.

Overlap detection

welcomer warns when two reservations for the same property have overlapping dates. Warnings appear above the table regardless of the --days filter. Affected rows are highlighted in red.

Recipient extraction

Recipients are extracted from calendar events in this order:

  1. ATTENDEE entries
  2. ORGANIZER if no attendees
  3. SUMMARY (name) + Description field (email via Email:, phone via Telefon:) as last resort

Phone and email parsed from Description are available in all three cases.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

welcomer-0.4.2.tar.gz (60.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

welcomer-0.4.2-py3-none-any.whl (22.2 kB view details)

Uploaded Python 3

File details

Details for the file welcomer-0.4.2.tar.gz.

File metadata

  • Download URL: welcomer-0.4.2.tar.gz
  • Upload date:
  • Size: 60.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for welcomer-0.4.2.tar.gz
Algorithm Hash digest
SHA256 74f29bb67a4e7774f88f0c8a6ce9f042a41d3d692c38940b7a0f77319fa43349
MD5 62d7016874bc463c1fecea73c685676c
BLAKE2b-256 bd793fb1134cd0d0185a7b30a9940dcc32aa1fec561a039ce2bfc36b4a32bd8f

See more details on using hashes here.

Provenance

The following attestation bundles were made for welcomer-0.4.2.tar.gz:

Publisher: publish.yml on pdostal/welcomer

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file welcomer-0.4.2-py3-none-any.whl.

File metadata

  • Download URL: welcomer-0.4.2-py3-none-any.whl
  • Upload date:
  • Size: 22.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for welcomer-0.4.2-py3-none-any.whl
Algorithm Hash digest
SHA256 33d5d45f9eac8e2ada1fc9a7d5fa388ee7463150df2f5c33f88c52626ed44062
MD5 df601d4affe5dc1fa8605fd2e09bcd41
BLAKE2b-256 e578344e10bdd62e5c03ea4637c95e14665e0f9728cb53fceccf6a7c568320de

See more details on using hashes here.

Provenance

The following attestation bundles were made for welcomer-0.4.2-py3-none-any.whl:

Publisher: publish.yml on pdostal/welcomer

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page