How to implement a Custom Newsletter form with ConvertKit and Effect
Sandro Maglione
Check out my newsletter π¨βπ»Web development
30 November 2023[updated]
β’13 min read
Sandro Maglione
Web development
Learn how to use Effect to perform a request to subscribe a user to your newsletter on ConvertKit:
- Define component to collect user email
- Perform API request to sign up the user to the newsletter
- Implement the code to subscribe the user using Effect
Project configuration
The project uses nextjs
.
Note:
nextjs
is not required, but it is convenient since it allows to implement both client code and API routes
Run the create-next-app
command:
Note: The project on Github has no styling (no tailwindcss). This is intentional, since the focus is more on Effect and ConvertKit.
I suggest you to then switched to use pnpm
instead of npm
as package manager.
You can install
pnpm
from homebrew on my Mac:
You can add a preinstall
script to ensure that all command will be run using pnpm
instead of npm
:
Install dependencies
The dependencies for this project are effect
, @effect/schema
and @effect/platform
:
effect
: Core of the Effect ecosystem@effect/schema
: Define and use schemas to validate and transform data@effect/platform
: API interfaces for platform-specific services with Effect (used for HTTP requests in the project)
Run the install
command:
Folder structure
The project contains 2 main folders:
app
:nextjs
's page and layoutlib
: Implementation of the API to subscribe to the newslettertest
: Tests definition and configuration
There is more.
Every week I build a new open source project, with a new language or library, and teach you how I did it, what I learned, and how you can do the same. Join me and other 600+ readers.
Starting point: Services in Effect
The first step in all Effect projects is defining a Service.
A Service in Effect is a Typescript
interface
defining the API of the application.
Our application has only one method addSubscriber
that allows to subscribe an email to ConvertKit:
addSubscriber
returns an Effect
type. Effect.Effect<never, never, string>
has 3 type parameters:
- Requirements (
never
) - Errors (
never
) - Return value (
string
)
We are going to define these 3 types more specifically later on, for now we can leave the definition as
Effect.Effect<never, never, string>
Context for a Service
Every service in Effect is assigned a tag using Context
.
A Tag
serves as a representation of the ConvertKitService
service. It allows Effect to locate and use this service at runtime:
Note: We did not define yet a concrete implementation of the service. This is intentional. A service is just the definition of the API, we are going to implement it below using
Layer
Config: Environmental variables
A request to the ConvertKit API requires 3 configuration values:
- API URL (
https://api.convertkit.com/v3
) - Form ID
- API key
We define these 3 parameters using Config
from Effect.
First we define a class ConvertKitConfig
that exposes the parameters required for the request:
We then use Config
to collect apiKey
, url
and formId
and create an instance of ConvertKitConfig
:
Config.all
collects all the configuration values (in this case 3 strings defined usingConfig.string
)Config.map
extracts the values and converts them to a valid instance ofConvertKitConfig
Validate response using Schema
The ConvertKit API request when successful returns the information of the new subscribed user in the following format:
We use Schema
to validate the response type. In our example we collect only the two id
:
Tip: Check out QuickType to convert a JSON definition to its corresponding
Schema
The request also requires 2 parameters in the body:
api_key
email
We define these parameters using Schema
as well:
Request implementation: Layer and HttpClient
We are now ready to define the concrete implementation of ConvertKitService
using Layer
.
Layers are a way of separating implementation details from the service itself.
Layers act as constructors for creating the service.
We use Layer.succeed
since ConvertKitService
is a simple service without any dependencies.
Layer.succeed
requires the service Context
as first parameter and a concrete implementation of the service as second parameter (created using ConvertKitService.of
):
addSubscriber
implementation
addSubscriber
returns an Effect
. We use Effect.gen
to create it:
The first step is collecting the configuration parameters defined previously using Config
. We use Effect.config
to extract a valid instance of ConvertKitConfig
:
The second step is defining the request. We use HttpClient
from @effect/platform
:
request.post
:POST
request at the given URLrequest.setHeaders
: Define headers for the requestrequest.schemaBody
: Provide the body of thePOST
request from aSchema
(SubscribeRequest
)
HttpClient
allows to define the request using a declarative style (URL, headers, body)
We can now use Http.client.fetch()
to send a fetch request using the req
we defined above:
Http.client.fetch()
usesfetch
to send the given requestresponse.schemaBodyJson
validates the response usingSchema
(SubscribeResponse
)
This is it. We can now also update the service interface
definition with all the errors and response type:
The Type Definition is all you need πͺ This interface tells you everything you need to know about the API using the Effect type ππΌββοΈ No dependencies βοΈ List all the possible errors β Returns a SubscribeResponse Type safe, easy to read and maintain π
Weekly project: @ConvertKit + @nextjs + @EffectTS_ Implement your custom newsletter form using NextJs, the ConvertKit API, and Effect Interested? I will share all open source and on my newsletter π sandromaglione.com/newsletter?refβ¦
There is more.
Every week I build a new open source project, with a new language or library, and teach you how I did it, what I learned, and how you can do the same. Join me and other 600+ readers.
API route handler
We are going to use ConvertKitService
inside a nextjs
API route.
A route provides a Request
. We now need to validate the request and extract the email
sent in the body.
We create a new main
function that returns an Response
inside Effect
:
The first step is extracting the body from the POST request. We define a new error for this operation using Data.TaggedError
:
We need to verify that jsonBody
contains a valid email. We define a new schema to validate jsonBody
:
We then use the schema to extract a valid email from the body:
Finally we can call addSubscriber
from ConvertKitService
and return a Response
:
Handle errors and dependencies
This code does not work yet. We need to handle all errors and provide all the dependencies to satisfy the return type Effect.Effect<never, never, Response>
.
main
has a dependency on ConvertKitService
. Therefore we use Effect.provide
to pass a valid instance of ConvertKitService
:
We then need to handle all errors:
Effect.catchTags
allows to handle specific errors from their_tag
Effect.catchAll
allows to handle all (remaining) errors at once
That's all! Now we can use and run this Effect
using runPromise
inside the API route:
Perform request on the client
The very last step is to implement the component to send the request to the API.
Before doing that we need to create a new Effect
. The user provides an email (string
) and the Effect
is responsible to make the API request:
The implementation is similar to before:
Config
to collect the endpoint of the APIHttpClient
to define the request, passing theemail
in the bodyHttp.client.fetch()
to perform the request
page
component
We can then call main
inside a react component, passing the email
provided by the user:
Inside onSubmit
we call main
using runPromiseExit
, which returns Exit
.
Exit
is similar toEither
: it has either aFailure
orSuccess
value.
Using Exit.match
we can provide a response to the user for both success and failure:
ConvertKit form id
Inside Config
we specify the ConvertKit form id (CONVERTKIT_FORM_ID
).
Every subscriber on ConvertKit is linked to a form. A form id is therefore required to subscribe a new email.
You need to create a form inside the "Landing Pages & Forms" section:
Create a form to subscribe a new user in your ConvertKit account
Create and open the page to edit the form, then click on the "Publish" button. You can find the id of the form you just created inside the popup:
You can find the form id inside the "Publish" popup
This is it!
Open the page, add your email, and sign up! All powered by ConvertKit and Effect!
How do we make sure it all works as expected? Testing!
Turns out testing becomes easy and natural when using Effect. We can inject custom Config
values for testing and we can use Mock Service Worker to mock HTTP requests.
π You can read the next article on how to use
vitest
andmsw
to test and Effect app
If you are interested to learn more, every week I publish a new open source project and share notes and lessons learned in my newsletter. You can subscribe here below π
Thanks for reading.