penna
API Reference

Create Subscriber

Learn how to add new subscribers to your newsletter programmatically.

Create Subscriber

Penna provides a simple REST API to integrate newsletter functionality directly into your application. This guide will show you how to authenticate and add subscribers programmatically.

Authentication

All API requests must be authenticated using your newsletter's Public Key. You can find this key in your newsletter settings dashboard.

Include the key in the x-penna-public-key header of your requests.

x-penna-public-key: penn_your_public_key_here

Important: Your Public Key is exposed to anyone who visits your website. While it creates a seamless integration, we recommend proxying these requests through your own backend API if you want to control rate limits or add additional spam protection layers before calling Penna.

Endpoints

Add New Subscriber

Adds a new subscriber to your newsletter. Subscribers are created with a "pending" status and must verify their email address through a double opt-in process before they can receive newsletters.

Endpoint: POST /api/v1/external/newsletters/subscriber/new

Headers:

  • Content-Type: application/json
  • x-penna-public-key: <YOUR_PUBLIC_KEY>

Body:

{
  "email": "user@example.com",
  "name": "John Doe"
}

Fields:

FieldTypeRequiredDescription
emailstringYesSubscriber's email address
namestringNoSubscriber's full name (useful for personalization)

Code Examples

Here is how you can integrate the "Subscribe" functionality in various languages.

React / Next.js

components/newsletter-form.tsx
import { useState } from "react";

export function NewsletterForm() {
  const [email, setEmail] = useState("");
  const [name, setName] = useState("");
  const [status, setStatus] = useState("idle");

  const subscribe = async (e) => {
    e.preventDefault();
    setStatus("loading");

    try {
      const res = await fetch(
        "https://api.penna.dev/api/v1/external/newsletters/subscriber/new",
        {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            "x-penna-public-key": "penn_abc1234567890", // Replace with your public key
          },
          body: JSON.stringify({ email, name }),
        },
      );

      if (!res.ok) throw new Error("Failed to subscribe");

      setStatus("success");
      setEmail("");
      setName("");
    } catch (err) {
      setStatus("error");
    }
  };

  return (
    <form onSubmit={subscribe} className="flex flex-col gap-2">
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Your name (optional)"
        className="border p-2 rounded"
      />
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Enter your email"
        required
        className="border p-2 rounded"
      />
      <button
        type="submit"
        disabled={status === "loading"}
        className="bg-black text-white p-2 rounded"
      >
        {status === "loading" ? "Subscribing..." : "Subscribe"}
      </button>
      {status === "success" && <p className="text-green-500">Subscribed!</p>}
      {status === "error" && (
        <p className="text-red-500">Something went wrong.</p>
      )}
    </form>
  );
}

HTML / JavaScript (Vanilla)

index.html
<form id="newsletter-form">
  <input type="text" id="name" placeholder="Your name (optional)" />
  <input type="email" id="email" placeholder="your@email.com" required />
  <button type="submit">Join Newsletter</button>
</form>

<script>
  const form = document.getElementById("newsletter-form");

  form.addEventListener("submit", async (e) => {
    e.preventDefault();
    const email = document.getElementById("email").value;
    const name = document.getElementById("name").value;

    try {
      const response = await fetch(
        "https://api.penna.dev/api/v1/external/newsletters/subscriber/new",
        {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            "x-penna-public-key": "penn_abc1234567890",
          },
          body: JSON.stringify({ email, name }),
        },
      );

      if (response.ok) {
        alert("Successfully subscribed!");
      } else {
        alert("Subscription failed.");
      }
    } catch (error) {
      console.error("Error:", error);
    }
  });
</script>

cURL

curl -X POST https://api.penna.dev/api/v1/external/newsletters/subscriber/new \
  -H "Content-Type: application/json" \
  -H "x-penna-public-key: penn_abc1234567890" \
  -d '{"email": "test@example.com", "name": "John Doe"}'

Python (Requests)

import requests

url = "https://api.penna.dev/api/v1/external/newsletters/subscriber/new"
headers = {
    "Content-Type": "application/json",
    "x-penna-public-key": "penn_abc1234567890"
}
data = {
    "email": "test@example.com",
    "name": "John Doe"
}

response = requests.post(url, json=data, headers=headers)

if response.status_code == 200:
    print("Subscribed successfully!")
else:
    print(f"Failed: {response.text}")

Response

Success (200):

Subscribers are created with a "pending" status and must verify their email address before becoming active.

{
  "success": true,
  "message": "Verification email sent. Check your inbox to confirm.",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "newsletterId": "660e8400-e29b-41d4-a716-446655440001",
    "email": "test@example.com",
    "name": "John Doe",
    "status": "pending",
    "createdAt": "2024-01-15T10:30:00Z",
    "updatedAt": "2024-01-15T10:30:00Z"
  }
}

Error Responses:

// Subscriber already verified and subscribed
{
  "success": false,
  "message": "Subscriber with this email already exists in the newsletter",
  "data": null
}

// Subscriber exists but verification pending - resends verification email
{
  "success": true,
  "message": "Verification email sent again. Check your inbox to confirm.",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "pending",
    "..."
  }
}

// Subscriber limit reached
{
  "success": false,
  "message": "Subscriber limit reached. Please upgrade your plan.",
  "data": null
}

// Missing required fields
{
  "success": false,
  "message": "Email is required",
  "data": null
}

Use Cases

Collect Names for Personalization

Including the name field allows you to personalize your newsletters:

// In your signup form
const formData = {
  email: userEmail,
  name: userName, // Optional but recommended
};

await fetch(API_URL, {
  method: "POST",
  headers: { "x-penna-public-key": PUBLIC_KEY },
  body: JSON.stringify(formData),
});

Later in your newsletters, you can use the name for personalization:

  • "Hi John," instead of "Hi there,"
  • "Welcome John to our community!"

E-commerce Integration

// After checkout
await addSubscriber({
  email: customer.email,
  name: `${customer.firstName} ${customer.lastName}`,
});

Event Registration

// After event signup
await addSubscriber({
  email: attendee.email,
  name: attendee.fullName,
});