You don't need to build an admin panel for your SaaS!
When you launch your product, having an admin panel is the least of your priorities.
Building features for your users is far more important and fun than building something only you would use, right?
After all, you can always access the database directly and tweak the records when needed.
But as the years go by, the user base grows, ticket requests flow, and you find yourself spending your productive days handling customer queries even though you have a support team. You can't give them database access or expect them to know SQL or Mongo.
And if you encrypt a user's Personal Identifiable Information (PII), which you must, databases become obsolete.
I knew having an admin panel was overdue, but I still didn't feel like building one.
Solution: Pick a CRM or unified inbox that has an API
Since 2020, we have been using Crisp as our centralized support hub for Publer: Live chat, emails, and social messages in one place.
Disclaimer: this is not a sponsored article
So, even if I decided to build an admin panel, which is more of building another SaaS, the support team would still have to navigate multiple dashboards to map the user data.
That's far from efficient. Ideally, you would want to have everything in one place.
After months of research, development, and integrations, here's how I delegated all customer-related actions to my team right within Crisp!
1. Sync your user data with Crisp
Consider Crisp an external duplicate database of your SaaS users, meaning you must update Crisp every time something changes.
Setting up Crisp is very easy, so I will skip this part
I added a new field to my user model in Ruby as the foreign key between the two databases.
field :crisp_id, type: String, default: nil
Then, I wrote a Sidekiq worker to call the Crisp API in the background every time the user, or any other models related to the user, are updated.
after_save do
Crisp::AddOrUpdateContactWorker.perform_async(id, crisp_id)
end
The worker will either create a new contact in Crisp for new registered users or update the contacts for the existing users.
module Crisp
class AddOrUpdateContactWorker
include Sidekiq::Worker
def perform(user_id, crisp_id)
@user = User.find(user_id)
@crisp_id = crisp_id
@website_id = ENV['CRISP_WEBSITE']
if @crisp_id
update_contact
else
add_contact
end
end
end
end
In Crisp, we store the main profile info such as email, name, avatar, and timezone.
person = { nickname: @user.name.presence || @user.email.split('@').first }
person[:avatar] = @user.picture if @user.picture.present?
person[:timezone] = @user.settings.timezone
user_profile = { email: @user.email, person: person }
$crisp.website.add_new_people_profile(website_id, user_profile)
# or
$crisp.website.update_people_profile(website_id, crisp_id, user_profile)
Most importantly, we store custom data crucial to both the support and marketing teams, such as the plan the user is on, how many social accounts they have, etc.
Because of the continuous syncing, all information our app sends to Crisp for a customer is available in real-time in the Crisp dashboard for our support team.
Once a user deletes their Publer account, this worker automatically removes it from Crisp.
before_destroy do
Crisp::RemovePeopleProfileWorker.perform_async(id, crisp_id)
end
As with most CRMs, Crisp charges per # of contacts, so why pay for deleted users?
2. Build widgets for customer actions
Reading and syncing the customer data was the easy part. The most challenging one was allowing my team to perform actions on a customer, such as issuing refunds, extending subscriptions, offering discounts, or even updating a customer's email.
A great thing about Crisp is its marketplace and the ability to build private actions through widgets, aka iframes.
This means I had to develop the interface and logic for each action and then have Crisp widgets load them as iframes. Example of an iframe URL:
https://app.publer.io/admin/invoices?user_id={conversation.contact_data.id}&operator_name={operator.first_name}&operator_email={operator.email}&operator_avatar={operator.avatar}
The parameters help our backend load the info for the correct user and log the operator details for transparency and accountability.
This public URL is safe to share because you also need a token that Crisp automatically passes when it loads the iframe
Here's an example of a basic HTML view I built for listing and refunding invoices.
The support team can access and use all these custom actions within Crisp without jumping through hoops.
领英推荐
Pro-tip: Have the backend send an automated message to the customer after an action is performed
I don't think programmers are the only ones who hate repeated actions.
This way, the support team won't have to type the same messages, such as "I just issued you a refund" or "I just created this coupon" over and over again repeatedly.
3. Bonus: Use Crisp for email marketing
Crisp is not a marketing platform per se. However, after rigorous research and not feeling like integrating and paying for another platform, I made Crisp campaigns work for our needs.
You can use a custom email template for campaigns, so I simply copied the HTML template we internally use for transactional emails, i.e. verifying your email address.
Although the rich editor is a bit basic, it allows you to compose and preview your email campaigns quickly.
Having dynamic variables from the custom data is a big plus for personalization.
One-shot Campaigns
We manually send these marketing emails, such as product updates or announcements, for which our users can opt-in.
The custom data stored in Crisp and continuous syncing help us target only the recipients who expect our email.
My only problem with Crisp is that emails are not sent based on the user's time zone. Which means that someone may get the email at 4 AM.
Automated Campaigns
Believe it or not, I had not touched the welcome email in 6 years because it was part of our hard-coded transactional emails and, thus, not user-friendly to modify.
These automatic emails are now sent to users through Crisp after they take action, in this case, joining our platform.
This can be enabled on Crisp by selecting the event from a pre-defined list.
The events can be defined or sent to Crisp using their API. Example:
Crisp::AddPeopleEventWorker.perform_async(user.id, event)
The above background worker is called for all kinds of events we care about, such as upgrades or cancellations.
We recently started sending emails with tips through Crisp to improve our onboarding process.
For example, you will automatically receive an email on how to use Publer to grow your LinkedIn account AS SOON AS you connect your LinkedIn account.
The best part is that the content of these emails can be easily modified at any time. We can add and remove new use cases on the go.
We can also track the performance, even though analytics is not Crisp's strongest aspect.
Thank you for reading!
The Crisp ecosystem has helped me save a lot of headaches and hours of work every day.
The best part is that even if we decide to switch CRMs in the future, the infrastructure I have built on our end can be easily ported over.
If you enjoyed this article, follow me at Ervin Kalemi and subscribe to my newsletter.
Topics I plan to cover next:
Software Developer at Vodafone Ziggo Netherlands
9 个月Interesante, kur thua ske nevoje per admin panel por ke Crisp, tek manaxhon dhe subscription e klientve?
Leading our team toward personal & professional success | Founder & CEO at Kalemi Travel, Kalemi B2B Booking System
9 个月Interesante! Une s'e dija qe mund te beheshin keto, jo me te di si t'i bej teknikisht ?? Te intereson nje part time job si konsulent? ??
Software Engineer, MBA
9 个月Shume bukur! Morem disa ide te reja
Co-founder & CEO at Presto | Building the Digital Backbone of the Future Dental Ecosystem | Raising SEIS/EIS.
9 个月Great article. As it happens I am evaluating few CRMs for something we are working on and Crisp IM seems to be flexible and customisable