ory/kratos · error

no courier channels configured for

Error message

no courier channels configured for: %s

What it means

When DispatchMessage needs a channel, the channels method iterates configured courier channels looking for a matching ID. If none matches, it returns this error naming the requested channel ID. It means the message references a channel that is not present in the courier configuration.

Solutions

  1. Add the missing channel with the referenced ID to the courier.channels config.
  2. Compare the message's channel ID in the courier_messages table with the IDs in your config.
  3. Ensure the same courier config is used across environments or migrate queued messages accordingly.
  4. Delete or reassign stale queued messages referencing removed channel IDs.

Example fix

// before (config.yaml)
courier:
  channels: []
// after
courier:
  channels:
    - id: sms
      type: http
      request_config:
        method: POST
        url: https://sms-provider.example/send
Defensive patterns

Strategy: validation

Validate before calling

configured := map[string]bool{}
for _, ch := range cfg.Courier.Channels { configured[ch.ID] = true }
if !configured[messageChannelID] {
    return fmt.Errorf("channel %q is not configured in courier.channels", messageChannelID)
}

Type guard

null

Try / catch

if err := courier.DispatchMessage(ctx, msg); err != nil {
    if strings.Contains(err.Error(), "no courier channels configured") {
        log.Printf("message %s references unconfigured channel: %v", msg.ID, err)
        // requeue, dead-letter, or fix config
        return nil
    }
    return err
}

Prevention

When it happens

Trigger: Dispatching a message whose channel/recipient configuration points to a channel ID that does not appear in the courier channels list in config (e.g. message stored for channel "sms_provider" while config only defines "sms").

Common situations: Renaming or removing a channel in config while old messages still reference the old ID, running with a minimal config that omits channels while templates specify them, or mismatched IDs between environments (staging vs prod config).

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


AI-assisted analysis of ory/kratos@b86338da04 (2026-09-07). Data as JSON: /api/errors/da5212f1abfaf714. Report an issue: GitHub.

Appendix: source

Thrown at courier/courier_dispatcher.go:43

	for _, channel := range cs {
		if channel.ID != id {
			continue
		}
		switch channel.Type {
		case "smtp":
			courierChannel, err := NewSMTPChannelWithCustomTemplates(c.deps, channel.SMTPConfig, c.newEmailTemplateFromMessage)
			if err != nil {
				return nil, err
			}
			return courierChannel, nil
		case "http":
			return newHttpChannel(channel.ID, &channel.RequestConfig, c.deps), nil
		default:
			return nil, errors.Errorf("unknown courier channel type: %s", channel.Type)
		}
	}

	return nil, errors.Errorf("no courier channels configured for: %s", id)
}

func (c *courier) DispatchMessage(ctx context.Context, msg Message) (err error) {
	ctx, span := c.deps.Tracer(ctx).Tracer().Start(ctx, "courier.DispatchMessage", trace.WithAttributes(
		attribute.Stringer("message.id", msg.ID),
		attribute.Stringer("message.nid", msg.NID),
		attribute.Stringer("message.type", msg.Type),
		attribute.String("message.template_type", string(msg.TemplateType)),
		attribute.Int("message.send_count", msg.SendCount),
	))
	defer otelx.End(span, &err)
	ctx = semconv.ContextWithAttributes(ctx, semconv.AttrNID(msg.NID))

	logger := c.deps.Logger().
		WithField("message_id", msg.ID).
		WithField("message_nid", msg.NID).
		WithField("message_type", msg.Type).
		WithField("message_template_type", msg.TemplateType).

View on GitHub (pinned to b86338da04)