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 signup 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.
Skip if you're adding this to an existing app.
If you want to follow this tutorial from scratch, perform the following steps.
Set up Next.js app with shadcn/ui -
Add components: npx shadcn-ui@latest add button input
Install icons pack: npm i lucide-react
You're good to go.
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
Copy and store safely.
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("<https://api.sendfox.com/contacts>", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.SENDFOX_TOKEN}`,
},
body: JSON.stringify({
email,
}),
});
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.
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
</Button>
);
const SendFoxForm = () => {
const [email, setEmail] = useState("");
const [status, setStatus] = useState<FormStatus>("idle");
const [errorMessage, setErrorMessage] = useState("");
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
setStatus("loading");
setErrorMessage("");
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) {
setStatus("success");
} else {
setStatus("error");
setErrorMessage(data.message || "Failed to subscribe");
}
} catch (error) {
setStatus("error");
setErrorMessage("An error occurred while trying to subscribe.");
}
};
return (
<div className="w-full">
<form
onSubmit={handleSubmit}
className="flex w-full max-w-md items-center space-x-2 mx-auto"
>
<Input
className="w-full"
type="email"
placeholder="Email"
name="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
disabled={status === "loading" || status === "success"}
/>
{status === "loading" ? (
<ButtonLoading />
) : (
<Button type="submit" disabled={status === "success"}>
{status === "success" ? "Subscribed!" : "Subscribe"}
</Button>
)}
</form>
<div className="pt-2 min-h-[1em]">
{(status === "idle" || status === "loading") && <p> </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.
</p>
)}
</div>
</div>
);
};
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:
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.
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.
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 it may not be the best choice if you're running a few projects with separate domains.
Subscribe to my profile by filling in your email address on the left, and be up-to-date with my articles! I will soon be releasing a crazy interesting project that uses this solution!
Don't forget to follow me on Twitter @horosin, and subscribe to my blog’s newsletter for more tips and insights!