Conversation
A portal ticket creates only the customer's own incoming message, so nothing outbound ever leaves and the customer has no mail to reply to. These examples fail on the current behaviour. The three existing examples that read the description off `messages.last` now name `messages.incoming.last`, since the acknowledgement lands after it.
…eated Submitting a ticket on the help center portal wrote only the customer's own incoming message. Nothing outbound ever left, so the customer had no mail to reply to, and the ticket page told them to "reply to the email we sent you" when no email had been sent. Any mail they wrote instead opened a new conversation. Ticket creation now adds an outgoing message that names the ticket number and quotes the subject and description. It goes out over whichever channel the portal's ticket inbox uses: the email inbox mails it directly and stamps the conversation Message-ID their client replies to, the widget fallback sends it through the continuity mailer. The message carries no sender, so it is not a human response and leaves first_reply_created_at and waiting_since alone.
yui0303
approved these changes
Sep 21, 2026
…veries With SMTP_ADDRESS unset, config/initializers/mailer.rb switches the test environment to :sendmail, so nothing reaches ActionMailer::Base.deliveries and the example failed on CI while passing locally. Assert the job enqueued for the acknowledgement, then render the mail through ConversationReplyMailer.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Someone who opens a ticket on the help center now gets a first email straight away. It names the ticket number and repeats the subject and description they submitted, and replying to it lands on the same ticket. Until now the portal created the ticket silently: the customer received nothing, and the ticket page told them to "reply to the email we sent you" when no email had been sent. Anything they did write arrived as a brand new conversation.
Why
Portal submission wrote only the customer's own incoming message. Chatwoot delivers mail for outgoing and template messages, so nothing left the system, and the customer's mail client had no Message-ID of ours to reply to.
Ticket creation now adds one outgoing message to the ticket conversation and lets the existing channel machinery deliver it. An email ticket inbox mails it through
Email::SendOnEmailService, which stamps theconversation/<uuid>/messages/<id>Message-ID the reply threads onto; the widget fallback picks it up through the continuity mailer. The message carries no sender, the same shape the Captain handoff message uses, sohuman_response?stays false andfirst_reply_created_atandwaiting_sincekeep reporting the ticket as unanswered by a human.Scope
Public::Api::V1::Portals::TicketsController#create_acknowledgement_messageand#acknowledgement_content, called from#build_ticket.public_portal.tickets.acknowledgementstrings inconfig/locales/en.ymlandconfig/locales/zh_TW.yml.messages.incoming.last, since the acknowledgement is the last message.Tradeoffs
A dedicated mailer would have kept the thread free of an extra bubble, but only a message on the conversation gets the threading headers and shows up for the agent and on the customer's ticket page. The copy stays plain text rather than markdown because the portal ticket page renders message content verbatim.
Blast Radius
Only the portal ticket path. Every other way a conversation starts is untouched. One extra outbound mail per submitted ticket.
How to reproduce
Submit a ticket on a portal whose ticket inbox is an email inbox, then look at the customer's mailbox. Before this change nothing arrives.
Verification
Full local stack (
docker compose, MailHog on:8025, portal ticket inbox set to an email inbox). Submitting the form before the change delivered 0 mails; after it, 1 mail tojane@example.com, subjectRe: Cannot log in,Message-ID: <conversation/6743ae9f-.../messages/12@support.localhost>, body carryingticket #2, the subject and the description. Repeated with the portal pointed at the widget inbox: the continuity mailer carries the same acknowledgement.bundle exec rspec spec/controllers/public/api/v1/portals/tickets_controller_spec.rbreports 42 examples, 0 failures; the two new examples fail on the parent commit.bundle exec rubocopclean on both changed Ruby files.🤖 Generated with Claude Code