Blog

Release announcements, helpful tips, and community discussion

10.1

Cerb (10.1) is a feature upgrade released on September 10, 2021. It includes more than 114 new features and improvements from community feedback.

To check if you qualify for this release as a free update, view Setup » Configure » License. If your software updates expire on or after December 31, 2020 then you can upgrade without renewing your license. Cerb Cloud subscribers will be upgraded automatically.

Important Release Notes

Search

No record limit in searches

There is no longer a results limit when using fulltext search filter. Instead, inefficient searches are now automatically aborted by the existing query time limits.

Previously, fulltext searches were limited to 5,000 results.

Fulltext negation

All fulltext search filters now support negation. This returns records that don’t match the given terms.

text:!(not these words)

Error on unknown filter

Search queries with unknown filters now return an error and no results.

Previously, unknown filters were ignored, which could have unintentional effects.

Message header searching

Added a new fulltext search index for common message headers: from, to, cc, delivered-to, x-forwarded-to, and x-mailer.

On message records, added search filters for:

  • header.cc:
  • header.deliveredTo:
  • header.from:
  • header.to:

Previously, it was possible to filter by the message sender, but not by destination mailbox (without using custom fields).

Result count performance

In worklist search results, the total results are now calculated in parallel with retrieving the current page of results.

Previously, the count was calculated after the page of results was returned, which resulted in a longer wait.

The result count has a shorter query timeout, and no longer blocks the page of results from being returned.

In some environments, displaying an unfiltered total with millions of records can be slow with a cold cache. This no longer blocks a search popup from opening.

Search usability

When searching a worklist, a spinner and “Searching, please wait…” message is now displayed.

New searches will be ignored until the current search finishes. This makes it more difficult for workers to inadvertently burden the database server with many copies of the same query.

Previously, the worklist just dimmed to 50% opacity, which wasn’t intuitive for many users that anything was happening.

Subtotals performance

The subtotals sidebar on worklists now loads simultaneously (asynchronously). This also helps avoid the issue where a complex query prevents a worklist from finishing loading after closing and reopening.

Previously, the worklist results and subtotals sidebar were calculated before any output was shown, which combined their max timeouts and could result in no output if the webserver hung up first.

Deep search watchers

On worklists, all record types with a watchers: filter now support deep searches using the worker records.

For instance:

watchers:(group:Support)

or:

watchers:(isDisabled:y)

It’s also still possible to use the shortcuts like watchers:any and watchers:none.

Interactions

await:duration: continuation

In interaction.worker automations, a new await:duration: continuation displays a message: and waits until: a given date/time or interval (e.g. 5 seconds).

This can be used to display a status update while waiting for a long-running asynchronous task to finish. For instance, an AWS Step Function for new account provisioning.



start:
  set:
    isPlaying@bool: yes
  
  while:
    if@bool: {{isPlaying}}
    do:
      # A suspenseful wait
      await/rolling:
        duration:
          message: Rolling...
          until: 3 seconds
      
      await/rolled:
        form:
          title: Results
          elements:
            say:
              content@text:
                You rolled a {{random(1,6)}}
                ====
            submit/prompt_continue:
              buttons:
                continue/yes:
                  label: Roll again
                  icon: playing-dices
                  icon_at: start
                  value: roll
                continue/no:
                  label: Quit
                  style: secondary
                  value: quit
      
      # If quitting, break the loop
      outcome/quit:
        if@bool: {{prompt_continue == 'quit'}}
        then:
          set:
            isPlaying@bool: no


await:form: custom submit buttons

In interaction.worker automations, during an await:form: continuation, a submit: may optionally specify any number of custom buttons.

Custom buttons can provide label, icon, icon position, style, and value. Each button is either a continue: or reset: type.

When clicked, a continue: button sets the submit: element’s key to its value.

There may be multiple continue buttons (e.g. yes/no, allow/deny). This is much simpler than using a sheet, and it no longer requires a second click to continue.

When submit:buttons: isn’t provided, the default ‘continue’ and ‘reset’ buttons are automatically added; and these can still be controlled with the submit:continue@bool: and submit:reset@bool: shortcuts.

The current alternative styles for buttons are ‘secondary’ (gray like reset) or ‘outline’ (blue like continue but not filled). This makes it easy to visually distinguish primary/default and secondary options.



start:
  await:
    form:
      title: Confirmation
      elements:
        say:
          content: # Are you sure?
        submit/prompt_confirm:
          buttons:
            continue/yes:
              label: Yes
              value: yes
            continue/no:
              label: No
              style: secondary
              value: no

await:form: element validation

In interaction.worker automations, when using an await:form: continuation, form elements may now include a custom validation@raw: option.

Automation scripting can run logic and any output is considered to be an error message.

The current element’s value can be compared by using its placeholder name (e.g. prompt_name).

Multiple conditions can be checked by using if...elseif logic.



start:
  await:
    form:
      elements:
        text/prompt_alias:
          label: Alias
          required@bool: yes
          placeholder@text:
            (letters, numbers, dashes; 3-32 characters)
          validation@raw:
            {% if prompt_alias != prompt_alias|alphanum('-') %}
            An alias may only contain letters, numbers, and dashes.
            {% elseif prompt_alias|length < 3 %}
            An alias must be at least three characters long.
            {% elseif prompt_alias > 32 %}
            An alias must be 32 characters or less.
            {% endif %}

await:form: fileUpload key expansion

In interaction.worker automations, in an await:form: continuation, fileUpload: elements now include full attachment record details for validation.

Previously, this kind of validation had to be done after the form validated and submitted.



start:
  await:
    form:
      elements:
        fileUpload/prompt_image:
          label: File
          required@bool: yes
          validation@raw:
            {% if prompt_image__context is empty or prompt_image_id is empty %}
            The file must be a valid image.
            {% elseif prompt_image_mime_type is not prefixed ('image/') %}
            The file must be an image ({{prompt_image_mime_type}}).
            {% elseif prompt_image_size > 50000 %}
            The image must be smaller than 50KB ({{prompt_image_size|bytes_pretty}}).
            {% endif %}

Improved sheet filtering

In interaction.worker automations, the search filter input is now more obvious in sheet: elements on await:form: continuations.

There is a magnifying glass icon with ‘Search’ placeholder text.

Previously, during usability testing, several users thought the filter input was used to create new interactions rather than choose them.

Improved interactions simulation

In the automations editor, when using the Run tab to simulate an interaction, copying the Output: to the Input: during an await:form: will automatically create placeholders for all form elements at the top of the new input.

This skips submit: elements without custom buttons, and sheet: elements without a selection: column.

On sheet elements, it uses a string default for single selection, and an array default for multiple.

Previously these had to be discovered and added manually.



start:
  await/ask:
    form:
      elements:
        text/prompt_name:
          label: Name:
          required@bool: yes
          type: freeform
        text/prompt_email:
          label: Email
          required@bool: yes
          type: email
  await/confirm:
    form:
      elements:
        say:
          content@text:
            Hi {{prompt_name}}!
            We've sent a verification email to {{prompt_email}}


Improved interactions usability

In interaction.worker automations, pressing the escape key asks for confirmation before closing the popup.

Previously, the interaction aborted, and the popup was closed as soon as escape was pressed – whether accidental or not.

Automations

Sequential set action

In automations, the set: command now substitutes placeholders sequentially rather than all at once. This means you can set a value in the first key and refer to that value in the second key.

Previously this required two separate set: commands.



start:
  set:
    a@int: 2
    b@int: {{a*2}}
    c@int: {{b*2}}
  return:
    answer@int: {{a + b + c}}

@optional annotation

In automations KATA, a new @optional key annotation is available. This removes a key if its value evaluates to blank.

For instance, in a record.update: a set of fields: should only include those with new values.

Previously this required logic for every optional field (usually an outcome: with a var.set:).



start:
  set:
    task_name: Get coffee
    task_status: open
  
  record.create:
    output: new_task
    inputs:
      record_type: task
      # See: https://cerb.ai/docs/records/types/task/
      fields:
        title@key: task_name
        importance@key,optional,int: task_importance
        status@key: task_status

In the above example, there is no task_importance placeholder. Since it’s marked @optional, rather than throwing an error the key is simply ignored when it’s null.

HTTP headers array

In automations, using the http.request: command, the headers: input now accepts either a string of newline-delimited header name/value pairs, or an array with header names as keys.


http.request:
  inputs:
    headers:
      Content-Type: application/json
      X-Requester: Cerb



http.request:
  inputs:
    headers@text:
      {% if content_type %}
      Content-Type: {{content_type}}
      {% endif %}
      X-Requester: Cerb

HTTP body auto-encoding

In automations, using the http.request: command, the body: now accepts either a string or an array.



http.request:
  inputs:
    body@text:
      This is the body content
      on multiple indented lines.

If the body is defined as a dictionary of key: value pairs, then it will automatically be encoded based on the Content-Type: header:

  • JSON (application/json)
  • YAML (text/yaml or application/x-yaml)
  • URL-encoded (application/x-www-form-urlencoded); or if the Content-Type: is omitted

This removes the need for extraneous set: commands to prepare the HTTP request.



http.request:
  inputs:
    headers:
      Content-Type: application/json
    body:
      person:
        name: Kina Halpue
        title: Customer Service Manager

Create events from chooser

In the Automation Events editor, when opening the automation chooser from the toolbar, it’s now possible to create a new automation from the sheet.

Previously, this only displayed existing automations and new ones had to be non-intuitively created from Search » Automations.

Error reporting

Improved error reporting in automations when siblings of the same type have duplicate names; including those with the same type and no names.

For instance, two sibling set: commands previously didn’t return an error, but only the second one was evaluated. This also catches the issue where someone accidentally provides more than one start: command.

The fix for this is to add names to sibling commands of the same type:


start:
  set/name:
    name: Kina Halpue
  set/title:
    title: Customer Support Manager

Automation Events

mail.draft

Added a mail.draft automation event to modify any property on a new or resumed draft after a worker clicks on a ‘Compose’ or ‘Reply’ button, but before the editor popup opens. Previously, these workflows used ‘Before composing/replying’ behaviors, which required fragile Javascript.

Guide: Add a required custom fieldset on ticket replies using automations

mail.draft.validate

Added a mail.draft.validate automation event for implementing interactive custom validators when composing and replying to email. For example: no recipients, profanity filter, missing attachment, using formatting in plaintext, etc. These automations have the same functionality as worker interactions.

Guide: Add custom interactive validators when composing and replying to email

mail.received

Added a mail.received automation event to react to received messages after they are appended to a ticket. For instance, sending an auto-reply confirmation to new tickets.

The code editor toolbar now includes an option to automatically generate an example auto-reply automation.

Guide: Send an automatic confirmation on new tickets using automations

mail.send

Added a mail.send automation event to modify sent message drafts before they are delivered. For instance, appending a unique survey link to only the sent html message (not text, nor the copy Cerb saves).

mail.sent

Added a mail.sent automation event to perform actions after an outgoing message is sent by a worker.

Data Queries

Grouping by distinct values in worklist.subtotals

In worklist.subtotals data queries, a by.distinct:[...] aggregate function is now available. This counts the number of distinct values in the final by: field.

For instance, in an of:message query, by.distinct:[created@day,worker,ticket] would return the number of distinct tickets replied to per worker per day. Multiple replies on the same ticket, by the same worker, on the same day, would only count as 1.

Mail

Custom fieldsets in pre-created drafts

Compose and reply drafts now properly save and resume custom fieldsets.

Keyboard shortcuts on compose and reply

In the compose and reply editors, added configurable keyboard shortcuts for common toolbar items (e.g. bold, insert link, insert image).

Download all attachments as ZIP

On messages on ticket profiles, in the ‘…’ menu, there’s a new ‘Download all (.zip)’ button when a message has attachments and the ‘zip’ PHP extension is installed/enabled. The button creates a single ZIP archive with the combined attachments and downloads it.

Time Tracking

See: https://github.com/cerb/cerb-release/discussions/38-Track-time-from-records-with-interactions

Timer persistence

Time tracking records are now created when the timer starts. Pausing a time tracking timer saves its progress to the server.

Resume existing time tracking entries

Time tracking timers can be resumed on previously saved records.

When resuming a time tracking timer from the editor, changes to fields and comments are now saved first. Previously these changes were lost when the timer was resumed.

Human-readable date entry

Time tracking time entry in human-readable units (e.g. “1 hour, 15 mins, 5 seconds”). Previously time entries only tracked in minutes.

Time tracking in seconds

Time tracking timers are now measured in seconds rather than minutes. This allows a previously saved timer to be resumed without rounding to the nearest minute. The elapsed minutes are still calculated and stored for backwards compatibility.

Full changelog

  • [Mail/Parser] Cerb now properly handles FormMail-style email headers, like From: customer@example.com (Customer Name).

  • [Toolbars] Added a draft.read toolbar for adding interactions to draft previews on ticket profiles.

  • [Mail/Compose] Compose drafts now properly save custom fields and fieldsets. [#1083]

  • [Mail/Drafts] Reply drafts properly save and reload custom fieldset values.

  • [Time Tracking] Time tracking records are now created when the timer starts. Pausing a time tracking timer saves its progress to the server. [#1243]

  • [Time Tracking] Time tracking timers can be resumed on previously saved records.

  • [Time Tracking] When resuming a time tracking timer from the editor, changes to fields and comments are now saved first. Previously these changes were lost when the timer was resumed.

  • [Time Tracking] Time tracking time entry in human-readable units (e.g. “1 hour, 15 mins, 5 seconds”). Previously time entries only tracked in minutes.

  • [Time Tracking] Time tracking timers are now measured in seconds rather than minutes. This allows a previously saved timer to be resumed without rounding to the nearest minute.

  • [Records/Search/Fulltext] In record search queries, fulltext filters can now use parentheses to specify a mix of phrases and terms. For instance, text:("an exact phrase" other terms).

  • [Sheets/Security] Sheets now automatically HTML escape all placeholders in templates by default. This can be overridden in certain situations with the |raw filter.

  • [Records/Search] There is no longer a results limit when fulltext searching using a MySQL index (rather than Elasticsearch). Instead, inefficient searches are now automatically aborted by the existing query time limits. Every filter is now involved in queries that mix fulltext and field filters. [#1445]

  • [Records/Search] All fulltext search filters (on MySQL indexes) now support negation. For instance, text:!(not these words)

  • [Search/Messages] Added a new fulltext search index for common message headers: from, to, cc, delivered-to, x-forwarded-to, and x-mailer.

  • [Records/Search/Messages] On message records, added a header.deliveredTo: search filter.

  • [Records/Search/Messages] On message records, added a header.from: search filter.

  • [Records/Search/Messages] On message records, added a header.cc: search filter.

  • [Records/Search/Messages] On message records, added a header.to: search filter.

  • [Storage/MySQL] Improved MySQL 8.x support by combining the id and chunk indices on the storage_ tables.

  • [Records/Profiles] Added profile pages for message records. This makes it possible to isolate a single message, which simplifies workflows like printing.

  • [Records/Profiles/Messages] On message profile pages, the ‘Ticket Conversation’ widget can now display a single message with full functionality (e.g. toolbars, reply, comment, attachments).

  • [Records/Profiles/Drafts] On draft profile pages, the ‘Ticket Conversation’ widget can now display a single draft with full functionality (e.g. resume, comment). [#1360]

  • [Platform/Sessions] Increased the possible length of IPs in sessions. The limit was previously 40 characters, but some IPv6 (e.g. IPv4 mapping) can be slightly longer. [#1460]

  • [Automations/Mail] Implemented the mail.draft automation event. Automations can modify any property on a new or resumed draft after a worker clicks on a ‘Compose’ or ‘Reply’ button, but before the editor popup opens. Automations on this event are cumulative – multiple automations can modify the draft, with subsequent changes on the same fields overwriting earlier ones. When setting custom_fields:, their uri field can be used as the key instead of IDs. New custom field changes are merged with existing fields. This event drastically simplifies workflows that modify drafts (e.g. always adding a Bcc: field). Previously, these workflows used ‘Before composing/replying’ behaviors, which required fragile Javascript.

  • [Automations/Mail] Implemented the mail.sent automation event. Automations can perform actions after an outgoing message is sent by a worker.

  • [Mail] Refactored mail sending functionality to generate content separately for every combination of saved/sent and html/text. For instance, a content modification can be targeted at only the ‘sent html’ part. Previously this was limited to saved/sent, but it couldn’t target html/text.

  • [Mail/Markdown/UX] When generating a plaintext body from a Markdown-formatted outgoing message, image tags with alt text are now properly matched and converted.

  • [Mail/Relays] Inbound messages from workers through the email relay now generate a draft before being parsed. If the message fails to send (e.g. SMTP issues), it will remain a draft and retry rather than disappearing.

  • [Automations/Mail] Implemented the mail.send automation event. Automations can modify sent message drafts before they are delivered. For instance, appending a unique survey link to only the sent html message (not text, nor the copy Cerb saves), setting custom fields, or adding custom mail headers to prevent Sendgrid from rewriting the Message-Id: header. Content modifications can target any combination of text/html on the sent/saved message.

  • [Search/UX] Search queries with unknown filters now return an error and no results. Previously, unknown filters were ignored, which could have unintentional effects.

  • [Custom Records/Performance] On custom records, added an index to improve performance on filtering by the name: field. Some people use the name as a lookup field which could have >100K records. [#1466]

  • [Records/Tickets/UX] On ticket profiles, messages in the conversation now have a button to open their card rather than directly opening the editor. This makes it easier to see message links, view the full profile, etc.

  • [Records/Tickets/UX] On ticket profiles, comments in the conversation now have a button to open their card rather than directly opening the editor. This makes it easier to see comment links, view the full profile, etc.

  • [Records/Tickets/UX] On ticket profiles, drafts in the conversation now have a button to open their card rather than directly opening the editor. This makes it easier to see draft links, view the full profile, etc.

  • [Worklists/Subtotals] In worklist subtotals, changed the default ‘none’ category to ‘(none)’ to differentiate it from a literal value of ‘none’.

  • [Worklists/Subtotals/Performance] The subtotals sidebar on worklists now loads simultaneously (asynchronously). Previously, the worklist results and subtotals sidebar were calculated before any output was shown, which combined their max timeouts and could result in no output if the webserver hung up first (usually after 30 seconds). This also helps avoid the issue where a complex query prevents a worklist from finishing loading after closing and reopening.

  • [Worklists/Subtotals/Performance] Fixed an issue where the worklist subtotals sidebar wasn’t constrained by a query timeout. On a complex query, this could result in a backlog of database queries and a worklist that doesn’t finish loading.

  • [Interactions/Await] On interaction.worker automations, added an await:duration: continuation. This displays a message: and waits until: a given date/time or interval (e.g. 5 seconds). This can be used to display a status update while waiting for a long-running asynchronous task to finish. For instance, an AWS Step Function for new account provisioning. [#1455]

  • [Automations] In automations, the set: command now substitutes placeholders sequentially rather than all at once. This means you can set a value in the first key and refer to that value in the second key. Previously this required two separate set: commands.

  • [Automations] In automations, using the http.request: command, the headers: input now accepts either a string of newline-delimited header name/value pairs, or an array with header names as keys.

  • [Automations] In automations, using the http.request: command, the body: now accepts either a string or an array. Arrays are automatically encoded to JSON (application/json) or YAML (application/x-yaml/text/yaml) with the appropriate Content-Type: header; and are otherwise URL-encoded (application/x-www-form-urlencoded). This removes the need for extraneous set: commands to prepare the HTTP request.

  • [Interactions] On interaction.worker automations, when using an await:form: continuation, form elements may now include a custom validation@raw: option. Automation scripting can run logic and any output is considered to be an error message. The current element’s value can be compared by using its placeholder name (e.g. prompt_name). Multiple conditions can be checked by using if...elseif logic.

  • [Interactions] On interaction.website automations, when using an await:form: continuation, form elements may now include a custom validation@raw: option. Automation scripting can run logic and any output is considered to be an error message. The current element’s value can be compared by using its placeholder name (e.g. prompt_name). Multiple conditions can be checked by using if...elseif logic.

  • [Interactions/Worker/UX] In worker interactions, the search filter input is now more obvious in sheet elements on await:form: continuations. There is a magnifying glass icon with ‘Search’ placeholder text. Previously, during usability testing, several users thought the filter input was used to create new interactions rather than choose them.

  • [Automations/Errors] Improved error reporting in automations when siblings of the same type have duplicate names; including those with the same type and no names. For instance, two sibling set: commands previously didn’t return an error, but only the second one was evaluated. This also catches the issue where someone accidentally provides more than one start: command. [#1462] [#1463]

  • [Automations/UX] In automations KATA, a new @optional key annotation is available. This removes a key if its value evaluates to blank. For instance, in a record.update: a set of fields: should only include those with new values. Previously this required logic for every optional field (usually an outcome: with a var.set:).

  • [Automations/Events] In the Automation Events editor, when opening the automation chooser from the toolbar, it’s now possible to create a new automation from the sheet. Previously, this only displayed existing automations and new ones had to be non-intuitively created from Search->Automations.

  • [Automations/Events/Mail] Implemented the mail.received automation event. Automations can react to received messages after they are appended to a ticket. For instance, sending an auto-reply confirmation to new tickets.

  • [Automations/Events/UX] In the mail.received automation event editor, the code editor toolbar now includes an option to generate an example auto-reply automation.

  • [Mail/Toolbars/Keyboard] In the compose and reply editors, added configurable keyboard shortcuts for common toolbar items (e.g. bold, insert link, insert image). [#1479]

  • [Mail/Downloads] On messages on ticket profiles, in the ‘…’ menu, there’s a new ‘Download all (.zip)’ button when a message has attachments and the ‘zip’ PHP extension is installed/enabled. The button creates a single ZIP archive with the combined attachments and downloads it.

  • [Search/Timeouts] Improved how timed out queries are canceled on the database server. Previously, the browser was properly displaying ‘timed out’, but the database server continued to run some queries. A new database connection is now used to terminate other threads.

  • [Search/Timeouts] When a search query times out, the full query is now logged in the PHP error log, along with the database name, process ID, and timeout limit. Previously, the logged query was limited to the error log max line length default in PHP (usually 1024 characters).

  • [Worklists/Search/Performance] In worklist search results, the total results are calculated in parallel with retrieving the current page of results. Previously, the count was calculated after the page of results was returned, which resulted in a longer wait. The result count has a shorter query timeout, and no longer blocks the page of results from being returned. In some environments, displaying an unfiltered total with millions of records can be slow with a cold cache. This no longer blocks a search popup from opening.

  • [Records/Contacts] On contact records, the primary email address must now be unique. [#1113]

  • [Records/Custom Fields] In custom field records, the URI field no longer allows dots. This interferes with key expansion in dictionaries.

  • [Records/Search/UX] When searching a worklist, a spinner and “Searching, please wait…” message is now displayed. Previously, the worklist just dimmed to 50% opacity, which wasn’t intuitive for many users that anything was happening.

  • [Records/Search/Performance] When searching a worklist, new searches will be ignored until the current search finishes. This makes it more difficult for workers to inadvertently burden the database server with many copies of the same query.

  • [Records/Watchers] On worklists, all record types with a watchers: filter now support deep searches using the worker records. For instance: watchers:(group:Support) or watchers:(isDisabled:y). It’s also still possible to use the shortcuts like watchers:any and watchers:none. [#591]

  • [Dashboards/Library/UX] In the workspace widget library, the examples for ‘Categories’ and ‘Sheet’ now default to created:"-1 year to now". Previously these used all available data, which could lead to timeouts in environments with millions of tickets.

  • [Records/Webhooks] Added the ‘Watchers’ column to webhook listener worklists.

  • [Portals] In community portal URIs, dashes (-) are now valid characters.

  • [Automations/Scripting] In automation scripting, added a strip_lines(prefixes) filter. This removes lines with the given prefixes. This is particularly useful for removing quoted text in compose/reply behaviors.

  • [Automations/Scripting] In automation scripting, added a tokenize filter. This converts a text block into an array of word tokens without spaces or punctuation.

  • [Automations/Syntax] On interaction.worker automations, added an obvious error when an await:form: element provides a non-string value to the validation: option. Previously this caused the interaction to end unexpectedly. The error is also logged on the automation.

  • [Automations/Interactions/UX] In interaction.worker automations, it’s no longer necessary to use headless: to denote that a non-interactive workflow shouldn’t open the interaction popup. This is now handled automatically when an automation exits in a state other than await:. Interactions now run on the server before the popup opens, which now provides the option to not open it at all.

  • [Automations/Interactions/UX] In interaction.worker automations, pressing the escape key asks for confirmation before closing the popup. Previously, the interaction aborted, and the popup was closed as soon as escape was pressed – whether accidental or not.

  • [Automations/Interactions] In interaction.worker automations, during an await:form: continuation, a submit: element may now specify any number of custom buttons. Previously, the submit: element was limited to the built-in ‘continue’ and ‘reset’ buttons. Custom buttons can provide label, icon, icon position, style, and value. Each button is either a submit:buttons:continue: or submit:buttons:reset: type. When clicked, a continue: button sets the submit element’s key to its value. There may be multiple continue buttons (e.g. yes/no, allow/deny). This is much simpler than using a sheet, and it no longer requires a second click to continue. When submit:buttons: isn’t provided, the default ‘continue’ and ‘reset’ buttons are automatically added; and these can still be controlled with the submit:continue@bool: and submit:reset@bool: shortcuts. The current alternative styles for buttons are ‘secondary’ (gray like reset) or ‘outline’ (blue like continue but not filled). This makes it easy to visually distinguish primary/default and secondary options.

  • [Automations/Mail/Validation] Added mail.draft.validate automations for implementing interactive custom validators when composing and replying to email. For example: no recipients, profanity filter, missing attachment, using formatting in plaintext, and so on. These automations have the same functionality as worker interactions. Interactive validators are configured on the mail.draft.validate automation event, and all enabled automations will run in sequence. Any validator automation can return:reject: to abort sending the message. Through interactivity, a validator can allow a worker to bypass a warning and continue sending; whereas non-interactive custom validators would reject with an error message that a worker would have to correct before continuing. For instance, A non-interactive validator would be problematic when suggesting that a worker may have omitted a mentioned “see attachment”, when the omission was intentional. Interactive validators can instead make suggestions which are accepted or ignored. While the most efficient option is to filter unneeded validators from the event, a mail.draft.validate automation that exits without an await: is silent and never opens the interaction popup. [#384]

  • [Mail/Automations/Validation] When composing mail without recipients, the ‘no recipients’ warning is now an interactive validator pre-configured on the mail.draft.validate event. Previously, a warning about not having a recipient was shown above the ‘Send’ button, but this was easily overlooked and couldn’t be disabled. As an interaction it’s completely user configurable – some environments may keep the warning but still allow tickets with no recipients, while others require recipients, and others yet disregard the warning entirely.

  • [Automations/Interactions/UX] In the automations editor, when using the ‘Run’ tab to simulate an interaction, copying the Output to the Input during an await:form: will automatically create placeholders for all form elements at the top of the new input. Previously these had to be discovered and added manually. This skips submit: elements without custom buttons, and sheet: elements without a selection: column. On sheet elements, it uses a string default for single selection, and an array default for multiple.

  • [Packages] Packages can now append bindings to existing automation events. This allows packages to fully configure an event-based workflow.

  • [Attachments/UX] Workers are now allowed to upload and send duplicate files with the same content and different filenames. Such dupes are still consolidated when received on inbound messages. This resolves an issue where early testers reported that filenames were mysteriously changing on uploaded files (because they were matching existing copies with a different name). A better solution would be to separate attachment metadata and content in the schema, then allow multiple metas to link to the same content ID while retaining their own name and owner.

  • [Automations/Interactions/UX] Moved 38 built-in helper interactions from interaction.worker to interaction.internal. This cleans up choosers on interaction.worker so users can find their own automations more easily. Removed the is_unlisted field from automations.

  • [Mail/Parser/Performance] When an HTML email message is received without a plaintext part, Cerb now generates up to 50KB of plaintext. Previously this was unbounded, so an erroneous or malicious HTML part could generate incredibly long messages.

  • [Automations/Validation] In automation inputs, increased the default max length of inputs:text: to 1,024 characters from 256. With type:url the default is 2,048 characters. The max length can be changed with the new type_options:max_length@int: option. As well, a new type_options:truncate@bool: option determines whether a longer input is truncated to fit (true, default) or returns a validation error (false).

  • [Automations/Validation] In automations, fixed an issue with the http.request: action where validation limited url: and headers@text:` to 255 characters each. The URL now defaults to 2,048 max length, and headers to 8,192.

  • [Automations/Validation] In automations, fine-tuned the max-length validation on text-based fields for all action inputs:.

  • [Automations/Interactions] In interaction.worker automations, in an await:form: continuation, fileUpload: elements now include full attachment record details for validation. If the element name ends with _id, then a corresponding __context key is added for key expansion. Otherwise, new keys are added for _id and _context with the name as the prefix. For example, fileUpload/prompt_file will set prompt_file and prompt_file_id to the ID and prompt_file__context to attachment; while fileUpload/prompt_file_id will set prompt_file_id and prompt_file__context. In both cases, you can expand keys like prompt_file_name and prompt_file_size in a validation@raw: script. Previously, this kind of validation had to be done after the form validated and submitted.

  • [Automations/Interactions] In interaction.worker automations, with await:record: continuations, the record editor now always opens in edit mode for existing records. Previously, this was opening existing records as card popups, which required an extra ‘Edit’ click.

  • [Mail/Automations] In mail.route automations, the parent_ticket_ placeholder is now available with key expansion.

  • [Data Queries/Subtotals] In type:worklist.subtotals data queries, a by.distinct:[...] aggregate function is now available. This counts the number of distinct values in the final by: field. For instance, on an of:message query, by.distinct:[created@day,worker,ticket] would return the number of distinct tickets replied to per worker per day. Multiple replies on the same ticket would count as +1. Thanks to 1Password for the feature request!

  • [Mail/Parser/Automations] When an empty ticket is first created through an automation or the API, and the first message is then later added (either through automations, API, or email parser), then new message events will now properly trigger. Previously, these events only triggered if the message created the ticket, but not when the ticket already existed and was empty. This simplifies procedural ticket creation workflows.

  • [Automations/Interactions] On interaction.worker automations, in a form:await: continuation, a text: element now has more permissive max lengths for URLs (2,048 characters) and freeform text (1,024). The max_length: option now properly controls validation.

  • [Records/Custom Fields] In custom field record validation, the uri field can no longer be entirely numeric. This makes it easy to distinguish IDs and URIs.

  • [Records/Drafts] Draft dictionaries now contain synthesized params keys for custom_fields_uri and message_custom_fields_uri. These contain the same values as custom_fields and message_custom_fields but are keyed by URI rather than ID. This makes comparisons much easier in automations. For instance, a mail.draft.validate automation that requires a custom field to be set before an email can be sent.

  • [Records/Drafts] When creating a draft record, the params of custom_fields and message_custom_fields can key custom fields on either ID or URI. This makes it much easier to create draft records from automations or the API.

  • [Webhooks/Automations] Fixed an issue with webhook portals where automation events didn’t have the request_ placeholders. Thanks to @mryanb for reporting!

  • [Records/Tasks] Task editors now prompt for confirmation before closing with the ESC key.

  • [Automations/Policies/UX] In the automation editor, the policy editor now autocompletes common deny: conditions for each action. For instance, denying an http.request: based on URL or HTTP method.

  • [Mail/HTML] Fixed an issue with displaying formatting on some HTML email messages (e.g. Outlook/Word). If the ‘tidy’ PHP extension was enabled, an XHTML conversion during cleanup could break the ‘css to inline styles’ step.

  • [Bots/Behaviors] In bot behaviors using the ‘Execute HTTP Request’ action, fixed an issue in the simulator when the ‘fileinfo’ PHP extension isn’t installed. Thanks to @abrenner for the bug report!

  • [KATA] Implemented the @trim annotation in KATA. This removes leading and trailing whitespace from a key’s value.

  • [KATA/Validation] When parsing KATA, an error is now thrown for unknown @attributes.

  • [KATA/Validation] KATA now returns an error on unexpected syntax. For instance, a non- key or value on a line, or indented multi-line text without @text on the key.

  • [KATA/Validation] KATA validation now ensures key names contain only letters, numbers, and underscores.

  • [Snippets/Explore] Implemented ‘Explore’ mode for snippet worklists.

  • [Interactions/UX] In interaction.worker automations, during an await:form: continuation, a textarea: element with a max_length: option now displays a live character count. [#1502]

  • [Automations/Validation] Before saving an automation record, the script syntax is now validated to catch common errors. [#1507]

  • [Automations/Validation] Before saving an automation record, the policy syntax is now validated to catch common errors. [#1461]

  • [Automations/Events/Validation] Before saving an automation event, the KATA syntax is validated to catch common errors. [#1506]

  • [KATA/Validation] KATA validation now properly returns an error when two sibling keys have the same type and name, but different @annotations.

  • [Toolbars/Validation] Before saving a toolbar, the KATA syntax is now validated to catch common errors.

  • [Snippets/Validation] Before saving a snippet record, the prompts KATA syntax is validated to catch common errors.

  • [Maps/Validation] Before saving a map widget on profiles or workspaces, the Maps KATA and Events KATA syntax is validated to catch common errors.

  • [Sheets/Validation] Before saving a sheet widget on cards, profiles, or workspaces, the Sheets KATA and Toolbar KATA syntax is validated to catch common errors.

  • [Dashboards] Before saving a workspace dashboard tab, Prompts KATA syntax is validated to catch common errors.

  • [KATA/Platform] Implemented a KATA validation service. This takes a document and schema in KATA format. The schema describes valid keys, attributes, and types. Validation supports multiple types per key (for instance, if a key can be a list or a string). The object type supports named attributes and dynamic attributePatterns. Attributes can be marked required@bool and multiple@bool (duplicable, nameable). Recursive validation be accomplished with a top-level definitions: schema key, and attributes can use the ref: option to reference them. An attribute using ref: can also define keys to override.

  • [Interactions/Websites] interaction.website automations in an await:form: continuation now default to 1,024 character lengths on text: elements and 2,048 characters on url:. Previously these were limited to 255 characters by default.

  • [Interactions/Automations] In interaction.worker automations, when using an await:form: continuation, text: and textarea: elements now support length_min@int:, length_max@int:, and truncate@bool options.

  • [Interactions/Automations] In interaction.website automations, when using an await:form: continuation, text: and textarea: elements now support length_min@int:, length_max@int:, and truncate@bool options.

  • [Interactions/Automations] In interaction.worker automations, when using an await:form: continuation, text: elements now support Emoji characters by default.

  • [Mail/Drafts] A mail.compose draft no longer requires a group_id parameter. This will use the default group if omitted. Thanks to @mryanb for the report.

  • [PGP] Importing a PGP public key no longer requires the ability to verify signatures. A public key can be used for just encryption.

  • [PGP] When importing or using a public key, subkeys are now properly ignored if they are expired, revoked, or bypassed.