instructure/canvas-lms · error · ConversationsHelper::Error

The following recipients have no active enrollment in the…

Error message

The following recipients have no active enrollment in the course, %{invalid_recipients}, unable to send messages

What it means

ConversationsHelper raises this ConversationsHelper::Error when validating recipients for a course-context conversation: one or more recipients lack an active enrollment in the course. The guard exists to prevent messaging users who are not currently enrolled, leaking or failing on stale recipient lists.

Solutions

  1. Filter recipients to users with an active enrollment in the context before sending (e.g. course.enrollments.active.where(user_id:) map).
  2. Refresh the recipient list from the API (GET /api/v1/courses/:id/enrollments) instead of a cached roster.
  3. If recipients should be valid, fix enrollment data: re-run SIS import or reinstate the enrollment.
  4. Catch ConversationsHelper::Error and surface the invalid_recipients names to the sender so they can deselect them.

Example fix

// before
@conversation = create_conversation(..., recipients: params[:recipients])
// after
invalid = ConversationsHelper.get_invalid_recipients(course, params[:recipients], current_user)
params[:recipients] = params[:recipients] - invalid.pluck(1)
raise if params[:recipients].empty?
@conversation = create_conversation(..., recipients: params[:recipients])
Defensive patterns

Strategy: validation

Validate before calling

active_ids = course.enrollments.active.pluck(:user_id)
bad = recipients - active_ids.map(&:to_s)
raise ArgumentError, "not enrolled: #{bad.join(',')}" if bad.any?

Type guard

def active_enrolled?(course, user_id)
  course.enrollments.active.exists?(user_id: user_id)
end

Try / catch

begin
  send_conversation(...)
rescue ConversationsHelper::Error => e
  render json: { errors: [{ attribute: e.attribute, message: e.message }] }, status: e.status
end

Prevention

When it happens

Trigger: POST to the conversations API with context = a course and recipients whose enrollments are completed, deleted, invited-but-not-active, or whose enrollment was conclude/soft-deleted after the recipient list was cached.

Common situations: Sending to a course section roster after term conclusion; messaging users imported via SIS whose enrollment sync has not run; stale client-side autocomplete lists; users who dropped the course between drafting and sending.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/1dae0321244f6e04. Report an issue: GitHub.

Appendix: source

Thrown at app/helpers/conversations_helper.rb:63

      raise ConversationsHelper::Error.new(message: I18n.t("Unable to create message without a body"), status: :bad_request, attribute: "empty_message")
    end

    recipients = normalize_recipients(
      recipients:,
      context_code:,
      conversation_id: conversation.conversation_id,
      current_user:,
      session:
    )

    if recipients && !conversation.conversation.can_add_participants?(recipients)
      raise ConversationsHelper::Error.new(message: I18n.t("Too many participants for group conversation"), status: :bad_request, attribute: "recipients")
    end

    invalid_recipients = get_invalid_recipients(context, recipients, current_user)
    unless invalid_recipients.to_a.empty?
      invalid_recipients = invalid_recipients.pluck(1)
      raise ConversationsHelper::Error.new(message: I18n.t("The following recipients have no active enrollment in the course, %{invalid_recipients}, unable to send messages", invalid_recipients:), status: :unauthorized, attribute: "recipients")
    end

    tags = infer_tags(
      recipients: conversation.conversation.participants.pluck(:id),
      context_code:
    )

    validate_message_ids(message_ids, conversation, current_user:)
    message_args = build_message_args(
      body:,
      attachment_ids:,
      domain_root_account_id:,
      media_comment_id:,
      media_comment_type:,
      current_user:,
      automated:
    )

View on GitHub (pinned to 1c9f0bb801)