hcengineering/platform · error

Failed to create channel for

Error message

Failed to create channel for ${normalizedEmail} in space ${space}: ${err instanceof Error ? err.message : String(err)}

What it means

fetchOrCreateChannel looks up a mail channel for a normalized email within a space, and if absent creates one. When the creation itself fails (the wrapped err — DB errors, permission issues, invalid space/email, transient conflicts), the original error is removed from cache and rethrown wrapped in this descriptive Error naming the email and space.

Solutions

  1. Inspect the wrapped err.message (embedded in this message) to find the root cause — it names the underlying failure.
  2. Verify the space exists and the email domain/address is valid before calling getOrCreateChannel.
  3. Retry on transient DB errors — the failed cache entry is already evicted so a retry re-attempts creation.
  4. Add a uniqueness-safe retry or upsert in createNewChannel to tolerate concurrent creation races.

Example fix

// before
const channel = await getOrCreateChannel(ctx, space, email) // throws on race
// after
let channel
try {
  channel = await getOrCreateChannel(ctx, space, email)
} catch (err) {
  await new Promise(r => setTimeout(r, 200))
  channel = await getOrCreateChannel(ctx, space, email)
}
Defensive patterns

Strategy: retry

Validate before calling

const spaceExists = await findSpace(ctx, space)
if (spaceExists === undefined) {
  throw new Error(`Space ${space} does not exist; not attempting channel creation for ${email}`)
}

Type guard

null

Try / catch

try {
  channel = await getOrCreateChannel(ctx, space, email)
} catch (err) {
  if (err.message.startsWith('Failed to create channel')) {
    console.error(`Channel creation failed (root cause: ${err.message}); retrying once`)
    await sleep(200)
    channel = await getOrCreateChannel(ctx, space, email)
  } else throw err
}

Prevention

When it happens

Trigger: Calling getOrCreateChannel where the underlying createNewChannel/factory call throws — e.g. the space doesn't exist, database connectivity failure, or a concurrent request racing to create the same channel.

Common situations: Inbound mail processing for an address whose space was deleted; transient DB outages during mail ingestion; duplicate concurrent webhook deliveries creating the same channel simultaneously; misconfigured mail routing producing emails for non-existent spaces.

Related errors


AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29). Data as JSON: /api/errors/5db257e341efec57. Report an issue: GitHub.

Appendix: source

Thrown at services/mail/mail-common/src/channel.ts:104

      if (channel != null) {
        this.ctx.info('Using existing channel', { me: normalizedEmail, space, channel: channel._id })
        return channel._id as Ref<Card>
      }

      return await this.createNewChannel(space, participants, normalizedEmail, personId)
    } catch (err) {
      this.ctx.error('Failed to create channel', {
        me: normalizedEmail,
        space,
        workspace: this.workspace,
        error: err instanceof Error ? err.message : String(err)
      })

      // Remove failed lookup from cache
      this.cache.delete(`${space}:${normalizedEmail}`)

      throw new Error(
        `Failed to create channel for ${normalizedEmail} in space ${space}: ${err instanceof Error ? err.message : String(err)}`
      )
    }
  }

  private async createNewChannel (
    space: Ref<Space>,
    participants: PersonId[],
    email: string,
    personId: PersonId
  ): Promise<Ref<Card>> {
    const normalizedEmail = normalizeEmail(email)
    const mutexKey = `channel:${this.workspace}:${space}:${normalizedEmail}`
    const releaseLock = await createMutex.lock(mutexKey)

    try {
      // Double-check that channel doesn't exist after acquiring lock
      const existingChannel = await this.client.findOne(mail.tag.MailThread, { title: normalizedEmail })

View on GitHub (pinned to 63e28dc964)