Pendro
Tutorials · 4-minute read

Embedding a Pendro newsletter form on any site (a 4.6 KB tour)

TL;DR — Drop a script tag pointing at `/sdk/v1/pendro.js` and a div with `data-pendro-newsletter` on any page. We mount a styled, validated, spam-protected widget. 4.6 KB gzipped, zero deps, any framework. Submissions land in the same inbox as in-site forms. Full snippet shown for HTML, Next.js, and Astro.

A developer's laptop with code on screen

Most newsletter signups on the internet are either (a) overweight (analytics + Mailchimp + tracking pixels = 80 KB of JS for a button) or (b) hostile (links you to an external signup page on the provider's domain, breaking the user's flow). Pendro's embed SDK is neither.

Drop two lines onto any page — yours or someone else's, in any framework or no framework at all — and you get a styled, validated, spam-protected newsletter widget that submits straight into the same Subscribers list you'd see in the dashboard. 4.6 KB gzipped. No external dependencies. Works on every modern browser back to mid-2020.

The simplest possible embed

This is the entire integration on a static HTML page:

<div data-pendro-newsletter
     data-project-id="cl9xs3..."></div>
<script src="https://pendro.co/sdk/v1/pendro.js" defer></script>

On script load, the SDK scans the document for elements with `data-pendro-newsletter`, mounts a styled form into each, and wires the submit handler to `POST /api/forms/[projectId]/subscribe` on Pendro. Submissions show up in your Subscribers inbox within seconds.

Customising via data attributes

The whole widget is configurable via data attributes — no JavaScript required. Want to change the placeholder text and the button label?

<div data-pendro-newsletter
     data-project-id="cl9xs3..."
     data-placeholder="you@company.com"
     data-button-label="Join the list"
     data-success-message="Welcome aboard. Watch your inbox."
     data-theme="dark"></div>

The full set of `data-*` overrides covers placeholder, button label, success / error messages, theme (`light` / `dark` / `auto`), required-field labels, and an optional `data-source` field that gets stored alongside the subscription (useful if you embed the same form on multiple pages and want to know which one converted).

Programmatic mount (for SPA frameworks)

If you're inside a single-page-app framework (React, Vue, Svelte), the auto-mount via script-tag scan only catches elements present at script-load time. For elements that mount later in the SPA lifecycle, call `mount()` explicitly:

import { mount } from '@pendro/sdk';

useEffect(() => {
  const widget = mount({
    target: '#my-newsletter',
    type: 'newsletter',
    projectId: 'cl9xs3...',
    onSuccess: (email) => {
      console.log('subscribed:', email);
      // run your own analytics / redirect / etc.
    },
  });
  return () => widget.unmount();
}, []);

The `mount()` API returns a handle with `.unmount()` so you can tear down the widget on component unmount. The `onSuccess` callback fires after the API call succeeds — useful for triggering analytics or showing your own success state.

Three framework examples

Vanilla HTML / static page

The auto-mount script-tag version above is all you need. No build step, no bundler, no install. Works on Webflow, on a static GitHub Pages site, on a one-off PHP page, anywhere.

Next.js

Use the Next.js Script component to load the SDK once globally, then sprinkle the `data-pendro-newsletter` div wherever you want a form:

// app/layout.tsx
import Script from 'next/script';

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        <Script src="https://pendro.co/sdk/v1/pendro.js" strategy="afterInteractive" />
      </body>
    </html>
  );
}

// any page.tsx
export default function Page() {
  return (
    <section>
      <h2>Subscribe to our newsletter</h2>
      <div data-pendro-newsletter data-project-id="cl9xs3..." />
    </section>
  );
}

Astro

Astro is interesting because most of the page is server-rendered with zero client JS. Use a `<script>` directive scoped to the component that uses the form:

---
// src/components/Newsletter.astro
const projectId = 'cl9xs3...';
---

<div data-pendro-newsletter data-project-id={projectId}></div>

<script>
  // The SDK is small enough that loading it inline per-component
  // is cheaper than a global async script in many Astro layouts.
  import 'https://pendro.co/sdk/v1/pendro.js';
</script>

What about the contact form?

The same SDK ships a contact-form variant. Swap `data-pendro-newsletter` for `data-pendro-contact` and add `data-fields` to declare which fields the form should ask for:

<div data-pendro-contact
     data-project-id="cl9xs3..."
     data-fields="name,email,phone,message"></div>

Submissions land in your project's Submissions inbox, with the same notification routing (email + Slack + web push) as in-site forms.

Spam protection

Every widget ships with two layers of spam protection built in:

  • Honeypot fields: Hidden form fields bots fill in and humans don't. Any submission with a non-empty honeypot is silently flagged as spam and never shows up in your inbox.

  • Cloudflare Turnstile: When configured (set `data-turnstile="<site-key>"`), the widget renders a Turnstile challenge that's invisible 95% of the time and blocks bots without showing them a captcha. The remaining 5% see a single checkbox.

Bundle size + performance

Real numbers from the shipped `pendro.js`:

  • IIFE bundle: 4.6 KB gzipped (16 KB raw).

  • ESM bundle: 5.3 KB gzipped.

  • Zero runtime dependencies. No React, no Lit, no preact. Just plain DOM.

  • Lighthouse impact: 0 points lost on any score we've measured.

The script is served with immutable cache headers and a one-year max-age, so on a repeat-visit page it's served from disk cache and adds zero milliseconds to the LCP. Updates are version-pinned at `/sdk/v1/pendro.js` — the `/v1/` prefix means we'll never push a breaking change to that URL; breaking changes land at `/sdk/v2/`.

Build your site in Pendro.

Pick a template, edit live, hit publish. No code, no hosting setup, no surprises.

Start free

Stay close

More posts like this in your inbox.

Occasional updates — new posts, product changes, and notes on what we're learning. One email every couple of weeks, never more.

No spam, ever. Unsubscribe in one click.

Embed a Pendro newsletter form on any site — SDK tour · Pendro