Plain changelog
Plain changelog
plain.co

Weekly Update — 20 September 2021

 

API

 

 

We've been taking on fairly large slices of the platform and product for a few weeks in a row so this last week was spent mostly wrapping up loose ends and making small improvements here and there.

We've also been hard at work on our chat widget, but it's not quite ready for demoing yet!

Improvements:

  • 👀 Timeline entries are now marked as read more granularly: If you only scroll only 3 of 5 messages into view, only those 3 will be marked as read.
  • 🏃‍♀️ To make chat feel super snappy, we now optimistically update the timeline when a chat message has been successfully sent. This makes it feel a lot faster and cuts out some ~300-500ms from the time it takes for the timeline to update from pressing send on a chat.
  • 🔄 We improved the Support App's scroll handing in the timeline to make pinning to the bottom (e.g. chat-style inverted scrolling) more reliable. Turns out… this is a Hard Problem ™️
  • ✅ We’ve also improved our handling of a number of edge cases and errors in our GraphQL subscription infrastructure.

 

New & shiny

  • ✨ We now have a new GraphQL subscription to subscribe to a specific customer. In the future, this will be used to update a customer's status or their details when they change.

Let there be Subscriptions!

 

☢️ Warning this is a technical one!

We want our support app to feel super fast and importantly make collaboration between team members really easy. Like for any productivity app, an important part of this for us is making sure all UI updates are in real-time. We want to make sure you know instantly if an advisor starts to reply to a customer or issues are opened or closed by a back-end system.

This week we concluded the first big step in this direction by implementing GraphQL subscriptions for a customer’s timeline. It's hard to screenshot live-ness since… it's the same UI but … live. So instead we wanted to share a little on how this works behind the scenes, and specifically some of the technical challenges of building this in our architecture.

Typically GraphQL subscriptions are over WebSockets. Handling many WebSockets connections at scale is not a trivial thing to architect and implement yourself on traditional architectures. This is due to the connections being persistent and not stateless like HTTP requests (hence why there are so many real-time SaaS providers out there!).

Since our current architecture is primarily using AWS serverless solutions like AWS API Gateway, AWS Lambda, DynamoDB, and Eventbridge, we decided to try to implement a scalable GraphQL Subscription over WebSockets solution using these serverless technologies. The main hurdle we encountered was that the open source NodeJS GraphQL libraries all assume a long-running stateful server holding on to a WebSocket connection. This doesn’t exist with AWS API Gateway and Lambdas, so it meant that we needed to significantly reengineer how these libraries handled WebSockets.

The rough outline of our solution is as follows (this deserves a longer more detailed blog post): When a client subscribes to a customer’s timeline, it establishes a WebSocket connection to AWS API Gateway. This invokes our Lambda function with a connection id and the payload of the message. The connection id and the GraphQL subscription then get stored in DynamoDB table. When a new timeline entry is added to a customer’s timeline, then an event is fired and an event handler Lambda queries the DynamoDB table for WebSocket connections that are subscribed to that customer’s timeline. If it finds any it then executes the GraphQL subscription with the event as an argument to get a result. Finally, we send each connection the GraphQL result and… we're done! Phew.

That's the happy path, but with a host of detailed connection and performance issues to handle this was not trivial to implement. Also, for a front-end to really feel live, it had to not only be told of new entries (the easy ones) but also when an entry was updated or removed and it had to handle out-of-order or duplicate entries.

This is the final schema we ended up with:

  enum TimelineEntryChangeType {
    ADDED
    UPDATED
    REMOVED
  }

  type TimelineEntryChange {
    changeType: TimelineEntryChangeType!
    timelineEntry: TimelineEntry!
    cursor: String!
  }

  type Subscription {
    timelineChanges(customerId: ID!): TimelineEntryChange!
  }

It's been really challenging but we're really happy with this first implementation - we've built a lot of infrastructure that is re-usable meaning that in the next few weeks and months we can roll out subscriptions for many other resources such as customers details, agent statuses, and anything and everything that needs to be real-time!

Fancy working on problems like this? We're hiring!

 

API

 

 

Weekly Update — 6 September 2021

 

This week, we've been busy advancing on two larger initiatives: introducing support for GraphQL subscriptions, and progressing on our client-side chat widget. We're excited to share more on both in the coming weeks.

In the meantime, we've made a ton of improvements to our support app, including lots of small but impactful UI updates:

Customer queue: we now show customer avatars, the latest message preview, as well as what's happened since you last looked at the customer, alongside a set of smaller alignment and spacing improvements.

image.png

Customer sidebar: Following on from last week's API work here, we now show the last contact channel, the wait time, as well as the unread count in the customer sidebar, alongside lots of other interface improvements.

image.png

 

App

 

 

Weekly Update — 31 August 2021

 

Chat widget foundations

We've started work on our client-side chat widget — what your customers will use to talk to you.

mock (3).png

Most existing offerings opt for a floating widget which you can integrate via a script tag. Those can be quite handy and simple to set up, but offer little control when they need to work as part of an existing web app. We're going to instead offer greater developer controls by providing an NPM package where companies can integrate the widget into their UI however they like — for example, by customising individual components, changing how and where it appears, theming it, and so forth.

In the past week, we've built some of the foundations for this:

  • Session token handling — the chat widget now accepts a signed JSON web token that identifies the customer and then exchanges that with via our customer API for a session token.
  • Receiving and sending chat messages — we now render chat messages and let your customers respond. Gotta start somewhere!
  • Marking as read – The widget now correctly marks messages as read… when they are read. This is important for notifications!

 

Other changes

  • Via our API you can now get summarised timeline stats per customer. This, for now, includes how long they have been waiting for a reply, what channel they last used to get in touch and also an unread count for all activity in the timeline.
  • API support for filtering customers by last contact channel, whether they are assigned to anyone, and by a specific assignee.

 

API

 

 

Weekly Update — 23 August 2021

 

Customer queue

We've built our first iteration of queues in our API and App. It shows you all customers who you are currently helping as a team broken down by status and assignment.

Screenshot 2021-08-23 at 13.54.22.png

As our very first pass of this, it's very limited in what it currently supports - in the next few weeks we plan on adding filtering and richer information on each customer such as their most recent message if and how long they've been waiting for a reply and much much more.

 

API

 

App

 

 

Weekly Update — 16 August 2021

 

In a lot of customer service platforms, an advisor is either online or offline. This doesn't really as a manager give you a great sense of whether someone is just having lunch, is helping a teammate out or… uhh.. doing other things 🚽.

To make this a bit better we allow advisors to explicitly go on a break as well as being offline. In the future, this will us to better handle customers replying or getting back in touch while you are working but just temporarily stepped away from your desk. Importantly it will also be able to feed into how reporting works and give managers a great overview of the team, especially in remote teams and during global pandemics.

 enum UserStatus {
    ONLINE
    OFFLINE
    BREAK
  }

 

Other changes

  • Feature: There is now a new unassignAllCustomers mutation to unassign all customers from a user. This will be used in cases where advisors go offline for extended periods of time and want to make sure their customers will be looked after by other advisors in the team.

 

API

 

 

Weekly Update — 9 August 2021

 

Two customers walk into a sidebar…

 

image.png

We've added a list that shows you all your currently assigned customers in the navigation sidebar, so you can easily jump between customers.

This is a super basic version for now, in the next few weeks we'll be making this a lot richer to show you the channel the user is using, unread counts and give you a general sense of priority!

 

Other Changes

Besides that this week we kicked off a few larger pieces of work such as GraphQL Subscription support and our embeddable widget libraries - more on that in the future 🤓

 

App

 

 

Weekly Update — 2 August 2021

 

It's new website day!

image.png

We've got a new website, name and domain, and we're super excited to share it with you. Head over to plain.co.

 

Other changes

  • Improvement: Our customer state machine implementation to be a bit sturdier and production ready
  • Improvement: We've sped up our API integration testing — we put a lot of work into making sure our API testing is rigorous and exhaustive. We'll write a blog post on this one day, it’s a super interesting topic!

Weekly Update — 26 July 2021

 

Slash commands

In a productivity tool, every mouse movement adds time and friction - so we're making sure the Plain app can be used without ever having to lift your fingers from your keyboard.

Inspired by Slack, you can now perform any given action from the composer. Simply hit / and use the arrow keys to open issues, resolve issues, mark customers as helped, and so forth.

image.png

 

Other changes

  • Improvement: Authentication for our customer GraphQL API now enforces the expiry of tokens provided by you. This is to ensure that you never, even by mistake, issue a customer token that permanently identifies a customer.
  • Improvement: We extended our API schema so that we can now attribute a timeline entry not only to a customer and user but also to an internal system. This will help us debug things when they don't quite work and inform reporting. This means that we now have a SystemActor along with UserActor and CustomerActor.
  • Improvement: Related to our state machine work, customer state transitions now show neatly in the timeline so you know what happened and when.
  • Improvement: If you try to mark a customer as helped with open issues, we now open a modal to allow you to resolve all open issues in one click first.

 

App

 

 

Weekly Update — 19 July 2021

 

Rise of the (state) machines

At the core of every good system is a good state machine, and that's what we've been building for the past few weeks. As a customer service platform, it's doubly important since it helps us answer the following questions per customer: What state are they in, why are they in this state, and when something happens, what state should they transition to?

In Plain, customers are in one of three states: Idle, Active and Snoozed.

When a customer is Active it means they are waiting to be helped. When they are Snoozed it means we are waiting for something to happen for them to be brought back to the attention of an advisor. When they are Idle it means that we think there is nothing left to do 😌

Our state machine is there to handle different events happening and consistently transition customers to the right state. For example, if an issue is opened, an Idle customer will be transitioned to Active. Importantly the state machine also enforces certain requirements such as preventing a customer with open issues to transition from Active to Idle. There are many many transitions and conditions - thank god for Whimsical.

While building this, we had an interesting debate as a team around how to best handle status in our app. We didn't feel that exposing customer status one-to-one from our API to the support app would offer a very good experience to first-time users. Equally, we were unsure about having one term within our API and another in our app since that could lead to an odd developer experience.

After some experimentation and testing, we ultimately decided that the context was so different between our API and the day to day work of an advisor that the best solution was to have different wording for the same statuses. That's how we ultimately ended up on:

image.png

Thanks to Aimee Quantrill for helping us iterate on the right language here and land on something that felt human and intuitive!

 

API

 

App