How to create a SendFox newsletter signup form in Next.js

How to create a SendFox newsletter signup form in Next.js

A practical guide to fill in a missing piece in the product docs


5 min read


SendFox is a popular newsletter service built by AppSumo. The company is famous for its store with lifetime deals for software. The product has limitations and is not a perfect solution but if you need a solid tool with predictable cost, it may be just the fit for you. There's a generous free tier as well.

There aren't many examples online for how to create a sign up form outside of embedding one. They don't look the best and don't play nice with JS frameworks.

Let's create a form component and an API route in Next.js that will give us full control.

I am using shadcn/ui for ui components and lucide-react for icons. You can easily swap these components for something else.

Set up

Skip if you're adding this to an existing app.

If you want to follow this tutorial from scratch, perform the following steps.

  1. Set up Next.js app with shadcn/ui - link

  2. Add components: npx shadcn-ui@latest add button input

  3. Install icons pack: npm i lucide-react

You're good to go.

SenfFox API

SendFox has a very basic API documentation, which you can find here:

You will need to get an API key from your SendFox account. Go to the settings, API section and click "Create new token".

Copy and store safely.

API route

Let's start with an API route that will handle our sign up logic.

You can add more verification to this code to pre-filter spammy sign-ups.

You may want to also save emails somewhere other than SendFox for redundancy.

I've created a file app/api/newsletter/route.ts with the following content:

export async function POST(request: Request) {
  const { email } = await request.json();

  if (!email) {
    return Response.json({ error: "Email is required" }, { status: 400 });

  const response = await fetch("<>", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${process.env.SENDFOX_TOKEN}`,
    body: JSON.stringify({

  if (!response.ok) {
    return Response.json({ error: "Failed to subscribe" }, { status: 500 });

  return Response.json({ message: "Subscribed successfully" });

You can test it in Postman or via curl or just jump in to create a form.

Form component

Create a file app/components/send-fox-form.tsx with the following content:

"use client";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Loader2 } from "lucide-react";
import { useState } from "react";

type FormStatus = "idle" | "loading" | "success" | "error";

const ButtonLoading = () => (
  <Button disabled>
    <Loader2 className="mr-2 h-4 w-4 animate-spin" />
    Please wait

const SendFoxForm = () => {
  const [email, setEmail] = useState("");
  const [status, setStatus] = useState<FormStatus>("idle");
  const [errorMessage, setErrorMessage] = useState("");

  const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {

    try {
      const response = await fetch("/api/newsletter", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        body: JSON.stringify({ email: email.trim().toLowerCase() }),

      const data = await response.json();
      if (response.ok) {
      } else {
        setErrorMessage(data.message || "Failed to subscribe");
    } catch (error) {
      setErrorMessage("An error occurred while trying to subscribe.");

  return (
    <div className="w-full">
        className="flex w-full max-w-md items-center space-x-2 mx-auto"
          onChange={(e) => setEmail(}
          disabled={status === "loading" || status === "success"}
        {status === "loading" ? (
          <ButtonLoading />
        ) : (
          <Button type="submit" disabled={status === "success"}>
            {status === "success" ? "Subscribed!" : "Subscribe"}
      <div className="pt-2 min-h-[1em]">
        {(status === "idle" || status === "loading") && <p>&nbsp;</p>}
        {status === "error" && (
          <p className="text-sm text-red-500 text-center">{errorMessage}</p>
        {status === "success" && (
          <p className="text-sm text-muted text-center">
            Subscription successful! Thank you for joining.

export default SendFoxForm;

The SendFoxForm component handles the subscription logic and user interaction. It utilizes useState to manage the form's status and user input. The form includes three states: idle, loading, and success, each guiding the user through the subscription process with appropriate feedback.

Business Logic Overview:

  1. Form Submission Handling:

    • When the form is submitted, it prevents the default form behavior and sets the status to loading.

    • The email input is trimmed and converted to lowercase before being sent to the server.

  2. API Interaction:

    • The form makes a POST request to the /api/newsletter route with the user's email.

    • If the response is successful (response.ok), the status changes to success.

    • If there's an error, the status changes to error, and an appropriate error message is displayed.

  3. User Feedback:

    • While the form is submitting, a loading button is displayed to inform the user to wait.

    • If the subscription is successful, a "Subscribed!" message is shown, and further input is disabled.

    • If there is an error, an error message is displayed, guiding the user to rectify the issue.


You now have a working SendFox newsletter sign up form in your Next.js app.

While SendFox is in no way perfect, it may be the right choice for your first newsletter or a side project.

Personally, I used it for one of my upcoming projects and it's been a good experience so far. It lacks features related to managing multiple lists of contacts so may not be the best choice if you're running a few projects with separate domains.

If you liked this content, please support me by sharing this post and subscribing to my Newsletter. I will soon be releasing a crazy interesting project that uses this solution!

You can also find me here:

Did you find this article valuable?

Support Karol Horosin: AI, Engineering & Product by becoming a sponsor. Any amount is appreciated!