Graphdocs

Features

Marketing API

Public subscribe and unsubscribe endpoints for email lists.

The Marketing API adds people to, and removes them from, the email lists you manage in the dashboard. Use it to power a newsletter signup form on your site; campaigns are then sent to the list from the dashboard.

These two endpoints are public: they take no API key, so you never have to expose one in a signup form.

Finding a list ID

  1. In the dashboard, open Marketing and select the email list.
  2. The list's page shows its List ID, with a button to copy it.

Only active lists accept subscribers.

Subscribe

POST /v1/marketing/subscribe
curl https://api.graphcms.io/v1/marketing/subscribe \
-X POST \
-H "Content-Type: application/json" \
-d '{
"listId": "YOUR_LIST_ID",
"email": "ada@example.com",
"firstName": "Ada",
"lastName": "Lovelace",
"source": "website-footer"
}'
Body propertyRequiredDescription
listIdyesThe list to add the subscriber to.
emailyesMust be a valid email address.
firstNamenoStored on the subscriber.
lastNamenoStored on the subscriber.
sourcenoWhere they signed up, for your own reporting. Defaults to api.
{
"success": true,
"message": "Subscribed successfully",
"subscriberId": "m4n5b6v7c8x9"
}

Subscribing is safe to repeat:

  • An email already subscribed to the list returns success: true with message: "Already subscribed".
  • An email that had unsubscribed is subscribed again, with message: "Resubscribed successfully".
StatusError
400Missing required fields: listId and email
400Invalid email address
404Email list not found or inactive

Unsubscribe

POST /v1/marketing/unsubscribe
curl https://api.graphcms.io/v1/marketing/unsubscribe \
-X POST \
-H "Content-Type: application/json" \
-d '{ "listId": "YOUR_LIST_ID", "email": "ada@example.com" }'
{ "success": true, "message": "Unsubscribed successfully" }

The subscriber is kept on the list, marked as unsubscribed, so they stop receiving campaigns but can resubscribe later. Errors are 400 with Missing required fields: listId and email, and 404 with Subscriber not found.

Calling it from your site

These endpoints don't send CORS headers, so a browser on your own domain can't call api.graphcms.io directly. Send the form to your own server (an API route, a server action or a serverless function) and call Graph from there. No key is needed, so there is nothing secret to protect, only the cross-origin rule.

A Next.js route handler:

app/api/subscribe/route.js
1export async function POST(request) {
2 const { email, firstName } = await request.json();
3
4 const response = await fetch('https://api.graphcms.io/v1/marketing/subscribe', {
5 method: 'POST',
6 headers: { 'Content-Type': 'application/json' },
7 body: JSON.stringify({
8 listId: process.env.GRAPH_LIST_ID,
9 email,
10 firstName,
11 source: 'website',
12 }),
13 });
14
15 return Response.json(await response.json(), { status: response.status });
16}

And a form that posts to it:

components/newsletter-form.jsx
1'use client';
2
3import { useState } from 'react';
4
5export function NewsletterForm() {
6 const [status, setStatus] = useState('idle');
7 const [message, setMessage] = useState('');
8
9 async function onSubmit(event) {
10 event.preventDefault();
11 setStatus('loading');
12
13 const form = new FormData(event.currentTarget);
14 const response = await fetch('/api/subscribe', {
15 method: 'POST',
16 headers: { 'Content-Type': 'application/json' },
17 body: JSON.stringify({ email: form.get('email'), firstName: form.get('firstName') }),
18 });
19 const result = await response.json();
20
21 setStatus(result.success ? 'success' : 'error');
22 setMessage(result.success ? 'Thanks! You are on the list.' : result.error);
23 }
24
25 return (
26 <form onSubmit={onSubmit}>
27 <input name="firstName" placeholder="First name" />
28 <input name="email" type="email" placeholder="Email" required />
29 <button type="submit" disabled={status === 'loading'}>
30 {status === 'loading' ? 'Subscribing…' : 'Subscribe'}
31 </button>
32 {message && <p role="status">{message}</p>}
33 </form>
34 );
35}

Plain HTML works the same way: point a form's fetch at your own endpoint, and have that endpoint forward the body to /v1/marketing/subscribe.