hcengineering/platform · error

Failed to create channel for ${normalizedEmail} in space ${s

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.

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)

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.