Skip to content

Commit c74262d

Browse files
committed
adding Listmonk integration
1 parent 1b5849c commit c74262d

16 files changed

Lines changed: 544 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Changelog
22

3+
# 2026-04-23
4+
- **Listmonk newsletter integration** — send newsletters to subscribers by composing an email to a virtual trigger address (e.g. `listmonk@ssp.sh`); neomd intercepts the send and creates a scheduled campaign in [Listmonk](https://listmonk.app) via its REST API instead of delivering via SMTP; configure multiple trigger addresses in `[[listmonk.triggers]]` to target different subscriber lists (newsletter, book, all); pre-send screen shows "Newsletter via Listmonk" with target list IDs and schedule delay; campaigns are created as draft then set to `scheduled` status with configurable delay (default 30 minutes); authentication via HTTP Basic Auth with environment variable expansion for API token; new self-contained `internal/listmonk/` package with full test coverage (httptest mocks); documented in `docs/integrations/listmonk.md`
5+
36
# 2026-04-21
47
- **RFC 5322 compliant Message-ID** — Message-IDs now use the sender's domain instead of hardcoded `@neomd`; ensures proper email threading, spam filter compatibility, and domain reputation consistency; uses `net/mail.ParseAddress()` for robust RFC 5322 address parsing; validates From address before sending and rejects invalid addresses that would result in `@localhost` Message-IDs; added comprehensive test coverage for BuildMessage, BuildDraftMessage, and BuildReactionMessage paths; documented email standards compliance in `docs/email-standards.md`
58
- **Fix: From validation allows local-only addresses**`extractDomain()` now returns `(domain, ok bool)` to distinguish between parsing failures (invalid address) and valid `user@localhost` addresses; validation only rejects unparseable addresses, not legitimate local mail system configurations; prevents regression that would have blocked valid RFC 5322 addresses

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ Or in Gmail:
156156
- **Headless daemon mode** — run `neomd --headless` on a server to continuously screen emails in the background without the TUI; watches screener list files for changes via Syncthing; emails are auto-screened every `bg_sync_interval` minutes so mobile apps see correctly filtered IMAP folders; perfect for running on a NAS while using the TUI on laptop/Android [more](https://ssp-data.github.io/neomd/docs/configurations/headless/)
157157
- **Kanagawa theme** — colors from the [kanagawa.nvim](https://github.com/rebelot/kanagawa.nvim) palette
158158
- **IMAP + SMTP** — direct connection via RFC 6851 MOVE, no local sync daemon required and keeps it in sync if you use it on mobile or different device [more](https://ssp-data.github.io/neomd/docs/configuration/)
159+
- **Listmonk newsletter integration** — compose an email to a virtual address (e.g. `listmonk@ssp.sh`) and neomd creates a scheduled campaign in [Listmonk](https://listmonk.app) via API instead of sending via SMTP; configure multiple trigger addresses to target different subscriber lists; pre-send screen shows campaign details; inspired by [HEY World](https://www.hey.com/world/) [more](https://ssp-data.github.io/neomd/docs/integrations/listmonk/)
159160
- **RFC 5322 compliant email delivery** — Message-IDs use sender's domain, proper MIME multipart/alternative structure (text/plain before text/html), quoted-printable encoding, and all required headers; ensures deliverability across all providers, spam filter compatibility, and correct email threading [more](https://ssp-data.github.io/neomd/docs/configurations/email-standards/)
160161

161162
> [!NOTE]

docs/content/_index.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,24 @@ toc: false
99
A minimal terminal email client&nbsp;<br class="sm:hx-block hx-hidden" />for people who read & write in Markdown
1010
{{< /hextra/hero-headline >}}
1111
</div>
12-
12+
<br>
1313
<div class="hx-mb-12">
1414
{{< hextra/hero-subtitle >}}
1515
Compose in Neovim, navigate with Vim motions, screen emails like HEY,&nbsp;<br class="sm:hx-block hx-hidden" />process your inbox with GTD — all from the terminal
1616
{{< /hextra/hero-subtitle >}}
1717
</div>
18-
18+
<br>
1919
<div class="hx-mb-6">
2020
{{< hextra/hero-button text="Overview and Philosophy" link="docs" >}}
2121
</div>
22-
22+
<br>
2323
<div class="hx-mt-6"></div>
24+
<br>
2425

2526
<div class="hx-mt-12 hx-mb-8">
2627
<h2 class="hx-text-4xl hx-font-bold hx-tracking-tight hx-text-gray-900 dark:hx-text-gray-50">What Makes neomd Different?</h2>
2728
</div>
28-
29+
<br>
2930
{{< hextra/feature-grid >}}
3031
{{< hextra/feature-card
3132
title="HEY-Style Screener"
@@ -75,6 +76,7 @@ YouTube rundown of most features:
7576
[![neomd demo](https://img.youtube.com/vi/lpmHqIrCC-w/maxresdefault.jpg)](https://youtu.be/8aKkldYLWV8)
7677

7778

79+
<br>
7880
<div class="hx-mt-12 hx-mb-8">
7981
<h2 class="hx-text-4xl hx-font-bold hx-tracking-tight hx-text-gray-900 dark:hx-text-gray-50">Documentation</h2>
8082
</div>
@@ -86,6 +88,7 @@ YouTube rundown of most features:
8688
{{< card link="docs/screener" title="Screener Workflow" subtitle="How to classify emails, bulk operations, and screener lists" >}}
8789
{{< card link="docs/reading" title="Reading Emails" subtitle="Navigation, images, links, attachments, threading" >}}
8890
{{< card link="docs/sending" title="Sending Emails" subtitle="Compose, attachments, CC/BCC, drafts, HTML signatures" >}}
91+
{{< card link="docs/integrations/" title="Integrations" subtitle="Integrations with Newsletter such as Listmonk" >}}
8992
{{< card link="docs/faq" title="FAQ" subtitle="Frequently asked questions" >}}
9093
{{< /cards >}}
9194

docs/content/docs/_index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ Or in Gmail:
159159
- **Headless daemon mode** — run `neomd --headless` on a server to continuously screen emails in the background without the TUI; watches screener list files for changes via Syncthing; emails are auto-screened every `bg_sync_interval` minutes so mobile apps see correctly filtered IMAP folders; perfect for running on a NAS while using the TUI on laptop/Android [more](https://ssp-data.github.io/neomd/docs/configurations/headless/)
160160
- **Kanagawa theme** — colors from the [kanagawa.nvim](https://github.com/rebelot/kanagawa.nvim) palette
161161
- **IMAP + SMTP** — direct connection via RFC 6851 MOVE, no local sync daemon required and keeps it in sync if you use it on mobile or different device [more](https://ssp-data.github.io/neomd/docs/configuration/)
162+
- **Listmonk newsletter integration** — compose an email to a virtual address (e.g. `listmonk@ssp.sh`) and neomd creates a scheduled campaign in [Listmonk](https://listmonk.app) via API instead of sending via SMTP; configure multiple trigger addresses to target different subscriber lists; pre-send screen shows campaign details; inspired by [HEY World](https://www.hey.com/world/) [more](https://ssp-data.github.io/neomd/docs/integrations/listmonk/)
162163
- **RFC 5322 compliant email delivery** — Message-IDs use sender's domain, proper MIME multipart/alternative structure (text/plain before text/html), quoted-printable encoding, and all required headers; ensures deliverability across all providers, spam filter compatibility, and correct email threading [more](https://ssp-data.github.io/neomd/docs/configurations/email-standards/)
163164

164165
{{< callout type="info" >}}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
title: Neomd Integrations
3+
weight: 30
4+
sidebar:
5+
open: false
6+
---
7+
8+
Specific integration for neomd, e.g. Listmonk as first one.
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
---
2+
title: Listmonk Newsletter Integration
3+
weight: 1
4+
---
5+
6+
Send newsletters to your subscribers by composing an email in neomd. Address it to a virtual trigger address (e.g. `listmonk@ssp.sh`), and neomd creates a [Listmonk](https://listmonk.app) campaign via API instead of sending via SMTP. Inspired by [HEY World](https://www.hey.com/world/).
7+
8+
## How it works
9+
10+
1. Compose an email as usual (`c`)
11+
2. Set the **To** field to a configured trigger address (e.g. `listmonk-newsletter@ssp.sh`)
12+
3. Write your newsletter in Markdown — it becomes the campaign body
13+
4. The pre-send screen shows **"Newsletter via Listmonk"** with the target list IDs and schedule delay
14+
5. Press `enter` — neomd creates a campaign in Listmonk and schedules it
15+
16+
The campaign is created as a draft, then immediately set to `scheduled` status. Listmonk handles the actual delivery to your subscribers (via Amazon SES or whatever messenger you configured).
17+
18+
## Configuration
19+
20+
Add a `[listmonk]` section to your `config.toml`:
21+
22+
```toml
23+
[listmonk]
24+
url = "https://list.ssp.sh"
25+
api_user = "sspaeti-api"
26+
api_token = "${LISTMONK_API_TOKEN}"
27+
delay_minutes = 30
28+
29+
[[listmonk.triggers]]
30+
address = "listmonk-newsletter@ssp.sh"
31+
list_ids = [2]
32+
33+
[[listmonk.triggers]]
34+
address = "listmonk-book@ssp.sh"
35+
list_ids = [4]
36+
37+
[[listmonk.triggers]]
38+
address = "listmonk@ssp.sh"
39+
list_ids = [2, 4] # send to all lists
40+
```
41+
42+
| Field | Description |
43+
|-------|-------------|
44+
| `url` | Base URL of your Listmonk instance |
45+
| `api_user` | API username for HTTP Basic Auth |
46+
| `api_token` | API token (supports `$ENV` expansion) |
47+
| `delay_minutes` | Minutes to delay before campaign sends (default 30) |
48+
49+
### Trigger addresses
50+
51+
Each `[[listmonk.triggers]]` entry maps a virtual email address to one or more Listmonk list IDs. You can configure multiple triggers to target different lists:
52+
53+
- `listmonk-newsletter@ssp.sh` → sends to your newsletter list only
54+
- `listmonk-book@ssp.sh` → sends to your book subscribers only
55+
- `listmonk@ssp.sh` → sends to both lists at once
56+
57+
The trigger address doesn't need to be a real mailbox — neomd intercepts it before any SMTP delivery.
58+
59+
### Getting your list IDs
60+
61+
List IDs are visible in the Listmonk admin UI, or via the API:
62+
63+
```bash
64+
curl -u "admin:token" https://list.ssp.sh/api/lists | jq '.data.results[] | {id, name}'
65+
```
66+
67+
## Pre-send screen
68+
69+
When the To address matches a trigger, the pre-send review changes:
70+
71+
- Header shows **"Newsletter via Listmonk"** instead of "Ready to send"
72+
- Displays the target **list IDs** and **schedule delay**
73+
- Help bar shows `enter schedule campaign` instead of `enter send`
74+
75+
76+
### How it looks
77+
78+
When sent in neomd:
79+
![listmonk](/images/listmonk-scheduled.png)
80+
81+
82+
And in Listmonk itself:
83+
![listmonk](/images/listmonk-scheduled-2.png)
84+
![listmonk](/images/listmonk-scheduled-3.png)
85+
86+
## Content
87+
88+
The email body (Markdown) is sent as-is with `content_type: "markdown"` — Listmonk converts it to HTML using its own template engine. The compose subject becomes the campaign subject.
89+
90+
![listmonk](/images/listmonk-scheduled-4.png)
91+
92+
## API details
93+
94+
neomd uses two Listmonk API calls:
95+
96+
1. `POST /api/campaigns` — creates campaign in DRAFT status with `send_at` set to now + `delay_minutes`
97+
2. `PUT /api/campaigns/{id}/status` — sets status to `scheduled`
98+
99+
Authentication is HTTP Basic Auth. The campaign name is auto-generated as `"{subject} - {timestamp}"`.
141 KB
Loading
114 KB
Loading
170 KB
Loading
162 KB
Loading

0 commit comments

Comments
 (0)