{"record":{"id":"72f6920602fb5e63","repo":"Budibase/budibase","slug":"imap-password-is-required-72f692","errorCode":null,"errorMessage":"IMAP password is required","messagePattern":"IMAP password is required","errorType":"http","errorClass":"HTTPError","httpStatus":400,"severity":"error","filePath":"packages/server/src/sdk/workspace/automations/crud.ts","lineNumber":380,"sourceCode":"  if (!trigger || !isEmailTrigger(trigger) || !trigger.inputs) {\n    return automation\n  }\n\n  if (getEmailTriggerAuthType(trigger.inputs) === EmailTriggerAuthType.OAUTH2) {\n    const hydratedAutomation = cloneDeep(automation)\n    const hydratedTrigger = hydratedAutomation.definition?.trigger\n    if (isEmailTrigger(hydratedTrigger)) {\n      delete hydratedTrigger.inputs.password\n    }\n    return hydratedAutomation\n  }\n\n  if (!isMaskedPassword(trigger.inputs.password)) {\n    return automation\n  }\n\n  if (!existing || !isEmailTrigger(existing.definition?.trigger)) {\n    throw new HTTPError(\"IMAP password is required\", 400)\n  }\n\n  const previousPassword = existing.definition.trigger.inputs?.password\n  if (!previousPassword) {\n    throw new HTTPError(\"IMAP password is required\", 400)\n  }\n\n  const hydratedAutomation = cloneDeep(automation)\n  const hydratedTrigger = hydratedAutomation.definition?.trigger\n  if (!isEmailTrigger(hydratedTrigger) || !hydratedTrigger.inputs) {\n    throw new HTTPError(\"IMAP password is required\", 400)\n  }\n  hydratedTrigger.inputs.password = previousPassword\n  return hydratedAutomation\n}\n\nfunction maskAutomationSecrets<T extends Automation>(automation: T): T {\n  const trigger = automation.definition?.trigger","sourceCodeStart":362,"sourceCodeEnd":398,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/server/src/sdk/workspace/automations/crud.ts#L362-L398","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Provide the real (unmasked) IMAP password in trigger.inputs.password when creating a new automation or after changing the trigger type.","Re-fetch the automation before editing so the trigger type matches and the masked password can be hydrated.","If the trigger type changed, clear the password field and require the user to re-enter it."],"exampleFix":"// before\ntrigger.inputs.password = \"****\" // masked, but automation is new\nawait update(automation)\n// after\ntrigger.inputs.password = realImapPassword\nawait update(automation)","handlingStrategy":"validation","validationCode":"const isMasked = (p?: string) => !p || p === MASKED /* per isMaskedPassword */\nif (isMasked(trigger.inputs?.password)) {\n  const existing = await sdk.automations.get(automation._id)\n  if (!existing || existing.definition?.trigger?.type !== trigger.type) {\n    // must send a real password\n    throw new Error(\"provide a real IMAP password\")\n  }\n}\nawait sdk.automations.update(automation)","typeGuard":null,"tryCatchPattern":"try {\n  await sdk.automations.update(automation)\n} catch (e) {\n  if (e instanceof HTTPError && e.status === 400 && e.message === \"IMAP password is required\") {\n    // prompt the user to enter the IMAP password in plain text\n  }\n  throw e\n}","preventionTips":["Require plain-text password entry for new automations and after trigger-type changes","Re-fetch the automation before editing to keep trigger type in sync","Don't round-trip masked values through clone/edit flows"],"tags":["validation","automations","secrets","imap","bad-request"],"backgroundTag":"masked-secret-placeholder-mismatch","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}