Budibase/budibase · error · HTTPError

IMAP password is required

Error message

IMAP password is required

What it means

Automations with an email (IMAP) trigger store the password masked. hydrateAutomationSecrets detects a masked password (isMaskedPassword) and must restore the real stored password from the existing automation; it throws this 400 HTTPError when no existing automation exists or its trigger is not an email trigger, so there is no password to unmask.

Source

Thrown at packages/server/src/sdk/workspace/automations/crud.ts:380

  if (!trigger || !isEmailTrigger(trigger) || !trigger.inputs) {
    return automation
  }

  if (getEmailTriggerAuthType(trigger.inputs) === EmailTriggerAuthType.OAUTH2) {
    const hydratedAutomation = cloneDeep(automation)
    const hydratedTrigger = hydratedAutomation.definition?.trigger
    if (isEmailTrigger(hydratedTrigger)) {
      delete hydratedTrigger.inputs.password
    }
    return hydratedAutomation
  }

  if (!isMaskedPassword(trigger.inputs.password)) {
    return automation
  }

  if (!existing || !isEmailTrigger(existing.definition?.trigger)) {
    throw new HTTPError("IMAP password is required", 400)
  }

  const previousPassword = existing.definition.trigger.inputs?.password
  if (!previousPassword) {
    throw new HTTPError("IMAP password is required", 400)
  }

  const hydratedAutomation = cloneDeep(automation)
  const hydratedTrigger = hydratedAutomation.definition?.trigger
  if (!isEmailTrigger(hydratedTrigger) || !hydratedTrigger.inputs) {
    throw new HTTPError("IMAP password is required", 400)
  }
  hydratedTrigger.inputs.password = previousPassword
  return hydratedAutomation
}

function maskAutomationSecrets<T extends Automation>(automation: T): T {
  const trigger = automation.definition?.trigger

View on GitHub (pinned to a81a902e9a)

Solutions

  1. Provide the real (unmasked) IMAP password in trigger.inputs.password when creating a new automation or after changing the trigger type.
  2. Re-fetch the automation before editing so the trigger type matches and the masked password can be hydrated.
  3. If the trigger type changed, clear the password field and require the user to re-enter it.

Example fix

// before
trigger.inputs.password = "****" // masked, but automation is new
await update(automation)
// after
trigger.inputs.password = realImapPassword
await update(automation)
Defensive patterns

Strategy: validation

Validate before calling

const isMasked = (p?: string) => !p || p === MASKED /* per isMaskedPassword */
if (isMasked(trigger.inputs?.password)) {
  const existing = await sdk.automations.get(automation._id)
  if (!existing || existing.definition?.trigger?.type !== trigger.type) {
    // must send a real password
    throw new Error("provide a real IMAP password")
  }
}
await sdk.automations.update(automation)

Try / catch

try {
  await sdk.automations.update(automation)
} catch (e) {
  if (e instanceof HTTPError && e.status === 400 && e.message === "IMAP password is required") {
    // prompt the user to enter the IMAP password in plain text
  }
  throw e
}

Prevention

When it happens

Trigger: create/update of an automation whose trigger.inputs.password is the masked placeholder, with either no existing automation for that _id, or the existing automation's definition.trigger failing isEmailTrigger.

Common situations: Client resubmits a masked password loaded from a GET response but the trigger type was switched (e.g. email -> webhook) before saving; creating a new automation by cloning a masked payload instead of entering a real password; stale client copy while another user changed the trigger type.

Related errors


AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29). Data as JSON: /api/errors/72f6920602fb5e63. Report an issue: GitHub.