Skip to main content

chatxpy

chatxpy

A lightweight Python tool for sending a single Markdown-formatted notification (subject, body, mentions, attachments) to Cisco Webex, Microsoft Teams, and Slack at once.

This is a Python port of chatxgo, preserving the same CLI flags, config file format, and Markdown behavior per chat tool.

chatxpy can be used in two ways:

  • As a standalone CLI tool — run the chatxpy binary (built with PyInstaller) directly from a shell script, CI job, cron task, etc.
  • As a Python library (chatxpy.notify) — import the notify package into your own Python program and call it directly, without going through the CLI or a config file at all.

Every chat tool is optional and independent: giving a tool's destination (*_DST) enables it, leaving it empty disables it, and a single chatxpy invocation delivers the same message to every tool that is enabled at once. A delivery failure on one tool does not stop delivery to the others.

Setup

Copy config.ini.example to config.ini and fill in the destinations/credentials for the chat tools you want to use. A tool is enabled simply by giving it a *_DST value; leave it empty to disable that tool.

WEBEX_TOKEN=...      # Webex bot/personal access token
WEBEX_DST=...        # destination roomId

MSTEAMS_DST=...      # Teams incoming webhook URL

SLACK_DST=...        # Slack incoming webhook URL
SLACK_TOKEN=...      # optional, needed only to upload local file attachments
SLACK_CHANNEL=...    # optional, channel for uploaded attachments

config.ini contains live credentials, so it is listed in .gitignore — never commit it. Commit config.ini.example instead.

Config file location and priority

When run as the CLI, chatxpy resolves config.ini in this order:

  1. A config.ini in the current directory takes priority.
  2. Otherwise, a per-user location is used:
    • Linux/macOS: ~/.config/chatxpy/config.ini
    • Windows: %AppData%\chatxpy\config.ini

Pass -config /path/to/config.ini to use a specific file instead of the resolved default. A missing config file is not an error — it simply leaves every chat tool disabled.

Profiles

config.ini can hold several named profiles as INI sections, so you can keep, for example, a personal and a work destination side by side in one file:

[default]
SLACK_DST=https://hooks.slack.example/default

[work]
SLACK_DST=https://hooks.slack.example/work

Select which profile to use with -profile/-p (default: default). Settings written before any [section] header belong to the default profile. Requesting a profile that doesn't exist in the file is an error.

CLI usage

chatxpy -subject "Deploy done" -body "**v1.2.3** shipped" -mention U0123456 -attach ./report.pdf
Option Shorthand Description
-subject -s Message subject/title
-body -b Message body, formatted as Markdown
-mention -m User to mention, as id or id:label (repeatable, or comma-separated). id is the native identifier for each tool: a Slack user ID, a Webex email/personId, or for Teams a Microsoft Entra object ID or user principal name/email.
-attach -a File path or URL to attach (repeatable, or comma-separated)
-config Path to the config.ini file (default: resolved as described in Setup)
-profile -p Profile (config.ini section) to use (default: default)
-debug Print verbose debug output
-u -update Self-update to the latest GitHub release
-v -version Show version information
-h -help Show usage information

A mention can be given as id:label, separating the identifier and the display name with a colon:

./dist/chatxpy -profile "work" -subject "Deploy done" -body "**v1.2.3** shipped" -mention "jane.doe@example.com:Jane"

Updating the CLI

Run chatxpy -update (or chatxpy -u) to check GitHub for a newer release of chatxpy and replace the currently running binary in place. This only replaces a PyInstaller-built binary from task build; it is a no-op concept when running from source via python -m chatxpy.

chatxpy -update

If the installed binary is already the latest version, chatxpy reports that and exits without changing anything.

Notes on sending to Microsoft Teams

  • Messages are sent to Teams as Adaptive Cards rather than the legacy MessageCard format. An Adaptive Card TextBlock supports a subset of CommonMark Markdown (bold, italic, bullet lists, numbered lists, links); headings, tables, images, and code blocks are not supported.
  • Incoming Webhooks that use Adaptive Cards officially support mentioning a user by either their Microsoft Entra object ID (a GUID) or their user principal name (UPN, typically their email address).

Library usage

Build a notify.Config directly in code; a tool is enabled by setting its dest.

from chatxpy import notify

cfg = notify.Config(slack=notify.SlackConfig(dest="https://hooks.slack.example/..."))
# or load a specific profile (section) from a config.ini file:
# cfg = notify.configfile.load_config_file("/path/to/config.ini", "work")
dispatcher = notify.Dispatcher(cfg)

results = dispatcher.send(
    notify.Message(
        subject="Deploy done",
        body="**v1.2.3** shipped",
        mentions=[notify.Mention(id="U0123456")],
        attachments=["./report.pdf"],
    )
)

Dispatcher.send returns one notify.Result(tool, error) per enabled tool; it raises notify.NoRecipientsError if no tool is enabled at all.

Markdown support per tool

The -body text is passed through to each tool largely as-is, but every chat tool renders its own dialect of Markdown, and none of them support the full CommonMark syntax. Write -body for the tool(s) you actually send to, and check the table below before relying on a given syntax.

Feature Cisco Webex Microsoft Teams (Adaptive Cards) Slack (mrkdwn)
Bold **bold** **bold** *bold* — CommonMark's **bold** is not supported and shows literally
Italic _italic_ _italic_ _italic_
Strikethrough Not supported Not supported ~strike~
Headings (#, ##, ...) #/##/### (h1–h3 only) Not supported Not supported
Blockquote (>) Supported Not supported Supported
Unordered list * item - item No native list syntax — write each line manually (e.g. • item)
Ordered list 1. item 1. item No native list syntax — write each line manually
Links [text](url) [text](url) Not [text](url) — must be <url + | + text>, or a bare URL
Inline code / code block Supported Not supported Supported
Horizontal rule (---) Supported Not supported Not supported
Tables Not supported Not supported Not supported
Images Not supported inline via Markdown Not supported Not supported inline (requires Slack Block Kit, not used by chatxpy)

Notable pitfalls:

  • Slack does not speak CommonMark. Slack's mrkdwn dialect uses single asterisks for bold and <url|text> for links, not double asterisks or [text](url).
  • Tables are unsupported everywhere — Webex, Teams, and Slack all lack table rendering in the message formats chatxpy uses.
  • Teams (Adaptive Cards) has the narrowest subset: only bold, italic, lists, and links render; headings, blockquotes, code blocks, horizontal rules, and tables are all shown as plain/literal text or dropped.

Installation

pip install chatxpy
# or, without installing it into your environment:
uvx chatxpy -subject "Deploy done" -body "**v1.2.3** shipped"

This installs the chatxpy console script plus the chatxpy/chatxpy.notify library. See Releasing to PyPI below for how new versions are published.

A standalone PyInstaller binary (no Python interpreter required) is also available from GitHub Releases; see task build below to build one yourself.

Development

Requires Python 3.13+ and uv.

task sync    # install dependencies
task build   # build a standalone binary with PyInstaller for the current platform
task test    # run the test suite
task run     # run from source (pass flags with: task run -- -subject hi -body world)
task debug   # run from source with -debug

PyInstaller does not cross-compile: task build only produces a binary for the platform it runs on. Build on each target OS (e.g. via a CI matrix) to release for multiple platforms.

Releasing to PyPI

  1. Bump version in pyproject.toml (uv's build backend does not derive it from git tags) and commit.
  2. Tag the release: git tag vX.Y.Z && git push origin vX.Y.Z.
  3. task dist — builds the sdist and wheel into dist/.
  4. task dist:check — validates the package metadata/long description with twine check.
  5. task publish:test — uploads to TestPyPI first, to dry-run the release.
  6. task publish — uploads to PyPI. This is irreversible: a given version number can never be re-uploaded, even if deleted.

task publish/task publish:test read credentials from UV_PUBLISH_TOKEN (a PyPI/TestPyPI API token), or use PyPI's trusted publishing from CI with no token at all.

License

MIT

Metadata

Release files for chatxpy 0.0.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for chatxpy 0.0.2
File Size Uploaded Provenance Actions
chatxpy-0.0.2.tar.gz 16.4 kB Without provenance Details for chatxpy-0.0.2.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for chatxpy 0.0.2
File Interpreter ABI Platform Actions
chatxpy-0.0.2-py3-none-any.whl Python 3 none any Details for chatxpy-0.0.2-py3-none-any.whl

Total release size: 38.9 kB

Release files / chatxpy-0.0.2.tar.gz

Download URL chatxpy-0.0.2.tar.gz
Size 16.4 kB
Tags Source
SHA-256 checksum
How to use checksums
d773812160b4f2714cbf0d54bbbe3bd5784ca8f22f99303bb10116fab1a9b3ac
BLAKE2b-256 checksum
How to use checksums
02ad95275361dc212db9f653a344613b8d48736a82bc6fbb7b9843230c1ddbe2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.0 {"installer":{"name":"uv","version":"0.12.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / chatxpy-0.0.2-py3-none-any.whl

Download URL chatxpy-0.0.2-py3-none-any.whl
Size 22.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
cdd0bbb113fb536d5a77793a7f396d450310954f6d4cdb1b1cdb3ec431f4f490
BLAKE2b-256 checksum
How to use checksums
7d8e05135fef616ffb5c03d90cfdb8f5bc0147af73627805e9ab03768922e9a5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.0 {"installer":{"name":"uv","version":"0.12.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for chatxpy 0.0.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.0.2 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page