Version 5.1.0
October 9, 2026
Added
- Reply by email. People can answer a conversation by replying to its notification email; the
reply is posted as theirs. It's off by default. Switch it on and set a reply mailbox in the
Reply by email section of Pigeon's settings, then point Postmark (basic auth), Mailgun
(signed, with a replay window and single-use tokens) or SendGrid (ECDSA-signed raw body, or
basic auth) at Pigeon's webhook. You can also poll an IMAP mailbox (
pigeon/inbound/poll, needs ext-imap) or pipe mail topigeon/inbound/import -. - Each participant gets their own reply address (
messages+<tag>@…), matched exactly, with theIn-Reply-To/Referencesheaders as a fallback. A reply only posts if it comes from that participant's address and they could post the same message on the site or in the control panel: a guest with a live link, a user still on the thread, or staff with Manage threads (and the user-to-user permission for direct threads). Pigeon doesn't start conversations from email. - Quoted history and signatures are stripped, HTML becomes escaped text, and out-of-office replies, bounces, mailing lists and Pigeon's own mail are ignored. Deliveries are deduplicated by Message-ID and limited per sender per hour. Attachments are screened against the attachment settings, with a content check, and anything refused is listed in an internal note.
- While reply by email is on, notification emails carry
Reply-To, a Message-ID Pigeon remembers, andAuto-Submitted, and say that replying to them works. pigeon/inbound/process,retry,logandpruneconsole commands, andInbound::EVENT_BEFORE_PROCESS/EVENT_AFTER_PROCESS.
Version 5.0.4
October 5, 2026
Staff with Access Pigeon no longer see user-to-user conversations. Grant the new View private user-to-user conversations permission to anyone who should. Attachments are now stored in
pigeon/<thread UID>/inside the attachment volume and downloaded through Pigeon, which checks who is asking. Files uploaded before 5.0.4 stay in the volume root. If that volume has public URLs, those files are still public; move them or the volume somewhere private. If your own templates link attachments withatt.asset.url, switch toactionUrl('pigeon/attachments/download', { id: att.id, access: token })(dropaccessfor signed-in users).
Security
- The guest rate limit was keyed on the visitor's address and the email they typed, so changing the email on every request was never limited. Every request sent a link email to the typed address with the visitor's subject in it. Guest threads and link requests now draw on three budgets: per client (the connecting address, with forwarded headers trusted only behind configured proxies, and IPv6 grouped by /64), per recipient, and site-wide. The guest-link email no longer includes the subject the visitor typed.
- Attachments were linked by their asset URL, so on a volume with public URLs a guest's upload was
a public file. Links now go through
pigeon/attachments/download, which serves a file to the guest holding that thread's link, a participant, or staff. Internal notes are staff-only, and files are always sent as downloads, never shown inline. Each thread's files go in a folder of their own. The settings screen warns when the attachment volume has public URLs. - Anyone with Access Pigeon could read two users' private conversation in the control panel, including in the inbox listing, and opening any thread silently made the reader a participant. User-to-user threads now need View private user-to-user conversations to read, list, reply to, assign or change. Opening a thread only marks it read for someone already in it; staff join a thread by replying, adding a note, or being assigned.
- Guest forms redirected to the
Refererheader. They now go back to the page the form was on, or to a hashedredirectinput.
Changed
- The control panel conversation view and the inbox widget use Craft's own CSS variables, status labels and form fields instead of hard-coded colours and inline styles.
helpers\RateLimiteris replaced byhelpers\RateLimit.
Version 5.0.3
August 26, 2026
Fixed
- The threads index returned HTTP 500 whenever the status column was shown. Craft 5 expects
statuses()to returncraft\enums\Colorcases; the string colours this plugin returned madeCp::componentStatusLabelHtml()fail with "Attempt to read propertyvalueon string", leaving the inbox blank with no error shown.