Reducing churn with Stripe and ChatGPT

Managing a SaaS is hard. Not only you need to develop an idea and battle for its adoption, constantly iterate and listen to user feedback, but also handle the back-office aspect of it. In particular, if you receive money in exchange of offered services, there is a plethora of zones to cover.

We tend to overspend efforts to attract new customers, while existing clients often overlooked. I, for one, am guilty of doing so. It may look counter-intuitive, but your existing customers are the ones you should be looking closer! Reducing churn is a guaranteed way to improve your compounded revenue over time.

Even if you have a customer hooked in a subscription model, the battle is not won. For one reason or another payments fail, and if you are not on top of your recurring customers, you are deemed to leave money in the table.

Automation is key for any small, fast-paced team, which is the standard in the SaaS industry. If you are able to offset boring back-office tasks to Pipedream, you gain precious time to invest on your core product.

Today I want to guide you on how to build a pipeline that:

  1. Listens to any failed payment events from Stripe;
  2. Uses ChatGPT to transform the often cryptic messages into friendly, human-readable emails;
  3. Sends the email via Postmark.

The goal is to give a personal touch to your customers, warming up the relationship when the unexpected happens, without losing the scalability that only an automated AI system can offer. Let's get to it!

Connecting to Stripe

For this guide we'll be connecting to Stripe, which is the leading platform for offering subscription-based products online.

Start by creating a new workflow and searching for Stripe as trigger. There are several interesting events that you can listen to, and today we'll select New Failed Payment.

Next, you will be prompted to connect your Stripe account. If you don't yet have one, now it's the time to go to the Developers section and select the API keys tab. I recommend you start by checking the Test mode toggle at the top-right corner, so you can test with peace of mind.

Click Create restricted key and select the permissions you want to allow. It's always a good practice to make them as restrictive as possible, especially for sensitive matters. Tip: if you are unsure on what to select, allow Read permission for the items on the Permissions column (the one in the left). It's not always clear what is needed for every webhook.

After you are done configuring your key, grab its value, add it to Pipedream, and once the connection is configured you will be greeted with a waiting screen.

Great! The connection is successful and now Pipedream is listening to failed payment events.

Triggering a failed payment

There are several ways to trigger a failed payment, and thankfully it doesn't involve using a real card in production. The simplest solution is to configure a Payment Link. Create one and you should see a portal like this:

Fun fact: even OpenAI uses Payment Links to manage ChatGPT subscriptions. You really don't need to build it yourself if you don't want to.

Now, Stripe conveniently provides several test card numbers that are wired to trigger specific errors (see the full list). Make sure you pick a card associated with a payment failure, provide any 3-digit number as CVC and select a valid date (anyone in the future is fine). Try to pay and if all goes well the payment will be declined and you should see it show up in Pipedream.

We have everything we need from Stripe, now onto leveraging AI to extract relevant information from the API.

Generating a human-friendly message

Stripe returns a huge JSON with several relevant metadata, including fields like failure_code and failure_message. But it also returns billing details, which holds the user name, email and address.

Technically we could write a code to extract everything needed, but that's a great use-case for an AI model like GPT-4o! Instead of fighting with all possible return cases, we should be able to feed the whole payload to the model and ask for a nicely-formatted JSON output.

In order to do that let's stringify everything in the event object of the trigger step. It's very easy to do it: hook a new step to the trigger and select Python. Add the following code:

def handler(pd: "pipedream"):
    # Reference data from previous steps
    event = pd.steps["trigger"]["event"]
    # Return data for use in future steps
    return {"event": str(event)}

This is all we need. The result will be a really long string, hard to parse for humans, but hopefully an easy task for an AI!

Let's move forward by hooking a new step: select OpenAI (ChatGPT) from the list and next the Chat option. Make sure you have your OpenAI API key at hand if you haven't connected to it already.

As of this writing, the most capable ChatGPT model is the GPT-4o. Select it from the dropdown. Next, in User Message, pick the event object returned in the previous step. That's how it should look like {{steps.python.$return_value.event}}.

Now, we want the AI to return 3 values:

  1. The email address of the user we will contact;
  2. The subject of the email;
  3. The content of the message.

This is a great use-case for using the json_object Response Format. By selecting this option we force the AI to return a parseable JSON, which will be handy later on.

The only thing left in this step is to write the System Instructions, which will nudge the model to answer the way we want. That's what I came up with:

Your job is to receive a JSON string, which happens when a Stripe payment fails, and craft an email that should feel personal, mention the reason the payment failed, suggest ways to fix it.

Extract the user email, create the subject, and the message, as it can be consumed by an API.

The tone of the message should be friendly, without being too much.

The response should also be in the JSON format and contain the fields: email, subject, message.

Important: you need to explicitly say in the system instructions that you want the response as JSON whenever the response format is set to json_object.

Go ahead and test the integration. If all goes well, you can inspect the content:

This is a good time for you to adjust the tone of the message to better suit your use-case. You can also experiment with other failures and analyze how the model performs.

Parsing the AI-generated JSON

Once you are happy with the way the AI is crafting the message we are ready to finish the workflow by emailing the user.

Before we connect to Postmark, which is my favorite email delivery service, we need to parse the JSON returned by the GPT model.

Add a new Python step (I named python_json) and paste the following code:

import json
def handler(pd: "pipedream"):
    # Reference data from previous steps
    content = pd.steps["chat"]["$return_value"]["original_messages_with_assistant_response"][2]["content"]
    # Return data for use in future steps
    return json.loads(content)

And now, as easy as that, the JSON is formatted and ready to be consumed by the email API.

Sending the email with Postmark

Let's wrap up this guide by creating our final step: search for Postmark and then select Send Single Email. Note: you can also choose Send Email With Template if you already have one configured.

The next steps are easy: connect to your Postmark account (you will need an API token for a server. It's also a good idea to have a dev server where you can experiment before going live). Finally, all required fields were generated in the previous step, with the exception of the "From" email address, which you should select from your Sender Signatures in Postmark.

Go ahead and test the integration. The email with the custom AI-generated message will be visible in Postmark.

Next steps

I hope that this guide sparks your creativity. While we barely touched all possible integrations made possible by Pipedream, a few follow-ups include: