用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/mikeckennedy/listmonk --skill listmonk命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
基于 SOC 职业分类
正在显示 SKILL.md
| name | listmonk |
| description | Listmonk Email API Client for Python. Use when writing Python code that uses the listmonk package. |
| license | MIT |
| compatibility | Requires Python >=3.10. |
Listmonk Email API Client for Python
pip install listmonk
| Need | Use |
|---|---|
| Look up a single subscriber | subscriber_by_email(email) / subscriber_by_id(id) / subscriber_by_uuid(uuid) |
| Filter subscribers by a custom attribute | subscribers(query_text="subscribers.attribs->>'city' = 'Portland'") |
| Unsubscribe someone but keep their record | block_subscriber(subscriber) |
| Erase a subscriber entirely | delete_subscriber(email) |
| Send a one-off email (reset code, receipt) | send_transactional_email(email, tx_template_id, template_data={...}) |
| Attach a file to a campaign | upload_media(path) then create_campaign(..., media_ids=[m.id]) |
| Schedule a campaign for later | create_campaign(..., send_at=datetime.now() + timedelta(hours=1)) |
| Send a test copy of a campaign to yourself | test_campaign(campaign_id, ['you@example.com']) |
| Actually send a draft campaign | start_campaign(campaign_id) — or set_campaign_status(id, CampaignStatuses.running) |
| Stop a campaign that is sending | pause_campaign(campaign_id) to resume later, cancel_campaign(campaign_id) to stop for good |
Point the client at your Listmonk instance and authenticate.
set_url_base: Set the base URL of your Listmonk instance for all subsequent callsget_base_url: Return the configured base URL of your Listmonk instancelogin: Log into Listmonk and cache the credentials for the life of your appverify_login: Verify that the stored login credentials are still valid at the serveris_healthy: Check whether the server is reachable and the stored credentials are validRead and manage mailing lists.
lists: Get all mailing lists on the serverlist_by_id: Get the full details of a single mailing list by its IDcreate_list: Create a new mailing list on the serverupdate_list: Update an existing mailing list on the serverdelete_list: Delete a mailing list by its IDCreate, query, update, and manage the status of subscribers.
subscribers: Get the list of subscribers matching the given criteria, or all subscribers if no criteria are givensubscriber_by_email: Retrieve a single subscriber by their email address (e.g. "some_user@talkpython.fm")subscriber_by_id: Retrieve a single subscriber by their numeric Listmonk ID (e.g. 201)subscriber_by_uuid: Retrieve a single subscriber by their UUID (e.g. "c37786af-e6ab-4260-9b49-740adpcm6ed")create_subscriber: Create a new subscriber on the Listmonk serverupdate_subscriber: Update many aspects of a subscriber: email and name, custom attribute data, list membership, and statusadd_subscribers_to_lists: Add a number of subscribers to a number of lists in a single bulk operationenable_subscriber: Set a subscriber's status to enabled so they will receive campaignsdisable_subscriber: Set a subscriber's status to disabled, pausing their subscription so they will not receive campaignsblock_subscriber: Add a subscriber to the blocklist, effectively unsubscribing them so they will not receive any mailconfirm_optin: Confirm a subscriber's opt-in to a list via the APIdelete_subscriber: Completely delete a subscriber from your system (as if they were never there)Create, preview, update, test, send, and delete email campaigns.
campaigns: Get all campaigns on the servercampaign_by_id: Get the full details of a campaign with the given IDcampaign_preview_by_id: Get the rendered preview of a campaign with the given IDcreate_campaign: Create a new campaign with the given parametersupdate_campaign: Update an existing campaign with the provided campaign informationtest_campaign: Send a one-off test copy of a campaign to specific addressesset_campaign_status: Move a campaign to a new lifecycle statusstart_campaign: Start sending a campaignpause_campaign: Pause a campaign that is currently sendingcancel_campaign: Stop a campaign for gooddelete_campaign: Completely delete a campaign from your systemUpload files to the media library to attach to campaigns.
upload_media: Upload a file to the Listmonk media libraryManage email templates and set the default.
templates: Retrieve all templates defined on the Listmonk instancetemplate_by_id: Retrieve a single template by its numeric IDtemplate_preview_by_id: Render and return a preview of a templatecreate_template: Create a new template on the Listmonk instanceupdate_template: Update an existing template on the Listmonk instanceset_default_template: Mark the given template as the default for its typedelete_template: Permanently delete a template from the Listmonk instanceSend one-off transactional messages.
send_transactional_email: Send a transactional email through Listmonk to a single recipientPydantic models returned by and passed to the API functions.
models.MailingListmodels.SubscriberStatusmodels.SubscriberStatusesmodels.Subscribermodels.CreateSubscriberModelmodels.Campaignmodels.CampaignStatusesmodels.CreateCampaignModelmodels.UpdateCampaignModelmodels.CampaignPreviewmodels.Templatemodels.CreateTemplateModelmodels.TemplatePreviewmodels.MediaErrors raised by the client.
errors.ValidationErrorerrors.OperationNotAllowedErrorerrors.ListmonkFileNotFoundErrorstatus on PUT /api/campaigns/{id}. Use set_campaign_status() (or start/pause/cancel_campaign()), which calls the dedicated /status endpoint.listmonk is a flat module with global auth state, not a client object. Configure the base URL, log in once, then call functions directly. set_url_base() must come before everything, and login() returns a bool (it does not raise on bad credentials) — check it.
import listmonk
listmonk.set_url_base('https://listmonk.yourdomain.com') # scheme required; no /api path
if not listmonk.login('admin', 'super-secret'): # False = rejected OR unreachable
raise SystemExit('Login failed: check credentials and base URL.')
# Add someone, then send them a transactional email.
sub = listmonk.create_subscriber('user@example.com', 'Jane Doe', list_ids={1}, pre_confirm=True)
listmonk.send_transactional_email('user@example.com', template_id=3, template_data={'name': 'Jane'})
Because auth is module-level global state, only one Listmonk instance can be targeted at a time and credential changes are not thread-safe. Every data call runs an internal state check and raises OperationNotAllowedError if the base URL is unset or you have not logged in.
This trips people up. Some functions raise on failure; others report failure through their return value. Do not wrap the "returns False" ones in try/except expecting an exception.
False (never raise on failure): login(), is_healthy(), verify_login() (rejected creds / unreachable), confirm_optin() (non-2xx status), add_subscribers_to_lists() (empty inputs or error status).None when nothing matches: subscriber_by_email(), subscriber_by_id(), subscriber_by_uuid(), campaign_by_id(), template_by_id(), and set_campaign_status() / start_campaign() / pause_campaign() / cancel_campaign() for an unknown campaign ID. test_campaign() returns False in that same case.ValueError for bad arguments, httpx2.HTTPStatusError on 4xx/5xx, and ValidationError on an empty/malformed server body. list_by_id() returns a MailingList (not Optional) and raises if the ID is missing.Note httpx2 (a fork of httpx with a near-identical API), not httpx: catch httpx2.HTTPStatusError and build timeouts with httpx2.Timeout(timeout=30.0).
update_subscriber, update_campaign, and update_template take the model object, not loose fields. Fetch it, mutate attributes in place, pass it back — the client sends the full record and re-fetches the server's fresh copy as the return value.
sub = listmonk.subscriber_by_email('user@example.com')
sub.name = 'Updated Name'
sub.attribs['rating'] = 7
# List membership: existing lists - remove_from_lists + add_to_lists
updated = listmonk.update_subscriber(sub, add_to_lists={4}, remove_from_lists={5})
update_subscriber(status=...) can enable/disable/block, but for status-only changes prefer the dedicated wrappers: enable_subscriber(sub), disable_subscriber(sub), block_subscriber(sub). Use block_subscriber to unsubscribe someone while keeping their record; use delete_subscriber(email) to erase them entirely.
subscribers(query_text=...) passes a server-side SQL-like filter over the subscribers table. Custom attributes are queried through the JSONB ->> operator. This requires the subscribers:sql_query permission on the user's role or the server returns 403.
listmonk.subscribers(query_text="subscribers.email = 'user@example.com'")
listmonk.subscribers(query_text="subscribers.attribs->>'city' = 'Portland'")
listmonk.subscribers(list_id=3) # list filter needs no permission
Listmonk renders templates with Go's text/template/html/template, so the syntax is {{ ... }} with a leading dot for context — not Jinja/Django. Every template body must contain the placeholder {{ template "content" . }} exactly once, or create_template() raises ValueError before any request is sent.
# Campaign body pulls subscriber fields from .Subscriber
body = '<html><body>Hi {{ .Subscriber.FirstName }}! {{ template "content" . }}</body></html>'
listmonk.create_template(name='Welcome', body=body, type='campaign')
There are two template types: 'campaign' and 'tx' (transactional). Merge data you pass to send_transactional_email(template_data=...) is available in a tx template as {{ .Tx.Data.<key> }}; subscriber fields are {{ .Subscriber.<Field> }}.
Attaching a file to a campaign is a two-step flow: upload to the media library, then reference the returned id. Attaching to a transactional email is inline via Path objects — no upload step.
from pathlib import Path
# Campaign attachment: upload_media() -> media_ids
media = listmonk.upload_media(Path('/path/to/report.png')) # or bytes + filename=
listmonk.create_campaign(name='Report', subject='This month', media_ids=[media.id])
# Transactional attachment: pass Paths directly
listmonk.send_transactional_email('user@example.com', template_id=3,
attachments=[Path('/path/to/invoice.pdf')])
update_campaign replaces the whole attachment set each call: with media_ids=None it re-sends the campaign's existing media, media_ids=[] clears them, and a new list swaps them. It also silently drops a send_at that is already in the past so a stale schedule doesn't fail the update. The default Listmonk server only allows image extensions in the media library, and there's no delete-media endpoint in this client.
Creating a campaign does not send it. A campaign starts as a draft and only begins delivering once its status becomes running, which is a separate endpoint — update_campaign ignores status.
from listmonk.models import CampaignStatuses
campaign = listmonk.create_campaign(name='June', subject='Our June Update', body='# Hi')
# Send a test copy first. Each address must already be a subscriber; the campaign
# stays a draft and its stats are untouched.
listmonk.test_campaign(campaign.id, ['you@example.com']) # -> True
listmonk.start_campaign(campaign.id) # or set_campaign_status(id, CampaignStatuses.running)
listmonk.pause_campaign(campaign.id) # halt, resumable with start_campaign()
listmonk.cancel_campaign(campaign.id) # stop for good
start_campaign, pause_campaign, and cancel_campaign are thin wrappers over set_campaign_status(campaign_id, status); reach for set_campaign_status directly for the other members of CampaignStatuses (draft, scheduled, finished). All of them return the updated Campaign, or None if no campaign has that ID. Prefer the enum over a raw string: an unrecognized status reaches Postgres and surfaces as an opaque HTTP 500. A rejected but valid transition (starting an already-finished campaign, say) raises httpx2.HTTPStatusError.
create_campaign(send_at=datetime.now() + timedelta(hours=1)) schedules a send. content_type is 'richtext' | 'html' | 'markdown' | 'plain' for campaigns; transactional email uses 'html' | 'markdown' | 'plain' and defaults to 'markdown'. Custom email headers are a list of single-entry dicts (e.g. [{'X-Priority': '1'}]), not a single dict.
Every page on the documentation site has a plain-Markdown twin: swap the .html extension for .md to get token-efficient source without the site chrome. For example https://mkennedy.codes/docs/listmonk/reference/subscribers.html is also available at https://mkennedy.codes/docs/listmonk/reference/subscribers.md. Prefer the .md form when reading these docs programmatically.