GetMessageWithContent returns a full message including raw_message and auth_headers. Marks the message as 'read' if it was 'unread'.
(ctx context.Context, messageID, agentID string)
| 2562 | // GetMessageWithContent returns a full message including raw_message and auth_headers. |
| 2563 | // Marks the message as 'read' if it was 'unread'. |
| 2564 | func (s *Store) GetMessageWithContent(ctx context.Context, messageID, agentID string) (*Message, error) { |
| 2565 | m := &Message{} |
| 2566 | var authHeadersJSON []byte |
| 2567 | var authVerdict []byte |
| 2568 | var outboundDeliveryStatus string |
| 2569 | // CTE so the read-marking UPDATE can still LEFT JOIN webhook_deliveries — |
| 2570 | // the detail view is a superset of the summary view, so it must carry the |
| 2571 | // same webhook_status/webhook_error the list exposes. Mirrors the |
| 2572 | // wd.status/wd.last_error JOIN used by GetMessagesByAgent/GetConversationByID. |
| 2573 | err := s.pool.QueryRow(ctx, |
| 2574 | `WITH upd AS ( |
| 2575 | UPDATE messages SET inbox_status = CASE WHEN inbox_status = 'unread' THEN 'read' ELSE inbox_status END |
| 2576 | WHERE id = $1 AND agent_id = $2 AND expires_at > now() |
| 2577 | AND NOT (direction = 'inbound' AND status IN (`+heldInboundStatuses+`)) |
| 2578 | RETURNING id, agent_id, direction, sender, recipient, to_recipients, cc, reply_to, subject, email_message_id, conversation_id, COALESCE(inbox_status, '') AS inbox_status, raw_message, auth_headers, auth_verdict, COALESCE(flagged, false) AS flagged, COALESCE(flag_reason, '') AS flag_reason, created_at, expires_at, labels, COALESCE(delivery_status, '') AS delivery_status, COALESCE(delivery_detail, '') AS delivery_detail, COALESCE(sent_as, '') AS sent_as, COALESCE(body_text, '') AS body_text, COALESCE(body_html, '') AS body_html, COALESCE(status, '') AS status |
| 2579 | ) |
| 2580 | SELECT upd.id, upd.agent_id, upd.direction, upd.sender, upd.recipient, upd.to_recipients, upd.cc, upd.reply_to, upd.subject, upd.email_message_id, upd.conversation_id, upd.inbox_status, upd.raw_message, upd.auth_headers, upd.auth_verdict, upd.flagged, upd.flag_reason, upd.created_at, upd.expires_at, upd.labels, upd.delivery_status, upd.delivery_detail, upd.sent_as, upd.body_text, upd.body_html, upd.status, COALESCE(wd.status, ''), COALESCE(wd.last_error, '') |
| 2581 | FROM upd LEFT JOIN webhook_deliveries wd ON wd.message_id = upd.id`, |
| 2582 | messageID, agentID, |
| 2583 | ).Scan(&m.ID, &m.AgentID, &m.Direction, &m.Sender, &m.Recipient, &m.ToRecipients, &m.CC, &m.ReplyTo, &m.Subject, &m.EmailMessageID, &m.ConversationID, &m.InboxStatus, &m.RawMessage, &authHeadersJSON, &authVerdict, &m.Flagged, &m.FlagReason, &m.CreatedAt, &m.ExpiresAt, &m.Labels, &outboundDeliveryStatus, &m.DeliveryDetail, &m.SentAs, &m.BodyText, &m.BodyHTML, &m.Status, &m.WebhookStatus, &m.WebhookError) |
| 2584 | if err != nil { |
| 2585 | return nil, err |
| 2586 | } |
| 2587 | // raw_message is loaded on the detail path, so size derives from it directly |
| 2588 | // (the summary path uses octet_length in SQL since it never loads the blob). |
| 2589 | m.SizeBytes = len(m.RawMessage) |
| 2590 | // DeliveryStatus is overloaded by direction (see Message.DeliveryStatus): |
| 2591 | // inbound carries inbox_status, outbound carries the delivery rollup. |
| 2592 | if m.Direction == "outbound" { |
| 2593 | m.DeliveryStatus = outboundDeliveryStatus |
| 2594 | } else { |
| 2595 | m.DeliveryStatus = m.InboxStatus |
| 2596 | } |
| 2597 | if authHeadersJSON != nil { |
| 2598 | if err := json.Unmarshal(authHeadersJSON, &m.AuthHeaders); err != nil { |
| 2599 | return nil, fmt.Errorf("unmarshal auth headers: %w", err) |
| 2600 | } |
| 2601 | } |
| 2602 | if err := unmarshalAuthVerdict(authVerdict, m); err != nil { |
| 2603 | return nil, err |
| 2604 | } |
| 2605 | return m, nil |
| 2606 | } |
| 2607 | |
| 2608 | // ErrLabelLimitExceeded reports that an add operation would push a |
| 2609 | // message past MaxLabelsPerMessage. Mapped to HTTP 400 at the handler. |