Skip to main content

Newsletter API

The Newsletter API allows users to subscribe and unsubscribe from the Artbase Gallery newsletter. Newsletter subscribers receive updates about new artists, collections, and featured work.


Endpoints

Subscribe to Newsletter

Adds an email address to the gallery newsletter.

Endpoint: POST /api/gallery/newsletter/subscribe

Authentication: None

Request Body:

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

Request Parameters:

FieldTypeRequiredDescription
emailstringYesEmail address (validated)
firstNamestringNoSubscriber's first name
lastNamestringNoSubscriber's last name

Response:

{
"success": true,
"subscriber": {
"id": "sub_abc123",
"email": "user@example.com",
"isActive": true,
"subscribedAt": "2024-01-15T10:30:00Z"
}
}

Response Fields:

FieldTypeDescription
successbooleanAlways true on success
subscriber.idstringSubscriber UUID
subscriber.emailstringEmail address
subscriber.isActivebooleanSubscription status
subscriber.subscribedAtstringISO 8601 timestamp

Example Request:

curl -X POST https://gallery.artbase.studio/api/gallery/newsletter/subscribe \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"firstName": "John",
"lastName": "Doe"
}'

Error Responses:

StatusErrorDescription
400Email is requiredMissing email
400Invalid email addressEmail validation failed

Special Cases:

  • Already subscribed - Returns existing subscriber, updates isActive: true
  • Previously unsubscribed - Reactivates subscription

Unsubscribe from Newsletter

Removes an email address from the gallery newsletter.

Endpoint: POST /api/gallery/newsletter/unsubscribe

Authentication: None

Request Body:

{
"email": "user@example.com"
}

Request Parameters:

FieldTypeRequiredDescription
emailstringYesEmail address to unsubscribe

Response:

{
"success": true
}

Example Request:

curl -X POST https://gallery.artbase.studio/api/gallery/newsletter/unsubscribe \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com"
}'

Error Responses:

StatusErrorDescription
400Email is requiredMissing email
404Email not foundEmail not in database

Note: Sets isActive: false rather than deleting record (for compliance and resubscription handling).


Email Validation

Emails are validated using standard regex:

function isValidEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}

Valid examples:

  • user@example.com
  • john.doe@company.co.uk
  • artist+gallery@studio.art

Invalid examples:

  • user@example (no TLD)
  • user@.com (no domain)
  • @example.com (no local part)

Newsletter Content

Subscribers receive periodic emails about:

  • New artists - When new artists join the gallery
  • Featured collections - Monthly curated collections
  • Artist spotlights - Stories and features
  • Seasonal updates - Holiday collections, sales
  • Gallery news - Platform updates, improvements

Frequency: 1-2 emails per month (not daily)


Client Integration

Newsletter Signup Form

import { useState } from 'react';

export function NewsletterSignup() {
const [email, setEmail] = useState('');
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
const [message, setMessage] = useState('');

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setStatus('loading');

try {
const res = await fetch('/api/gallery/newsletter/subscribe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, firstName, lastName }),
});

if (res.ok) {
setStatus('success');
setMessage('Successfully subscribed to the newsletter!');
setEmail('');
setFirstName('');
setLastName('');
} else {
const data = await res.json();
setStatus('error');
setMessage(data.error || 'Failed to subscribe');
}
} catch (error) {
setStatus('error');
setMessage('Network error. Please try again.');
}
};

return (
<form onSubmit={handleSubmit}>
<h3>Subscribe to Gallery Newsletter</h3>
<input
type="email"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
<input
type="text"
placeholder="First Name"
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
/>
<input
type="text"
placeholder="Last Name"
value={lastName}
onChange={(e) => setLastName(e.target.value)}
/>
<button type="submit" disabled={status === 'loading'}>
{status === 'loading' ? 'Subscribing...' : 'Subscribe'}
</button>
{message && (
<p className={status === 'success' ? 'success' : 'error'}>
{message}
</p>
)}
</form>
);
}
export function Footer() {
return (
<footer>
<div className="newsletter-section">
<h4>Stay Updated</h4>
<p>Get monthly updates on new artists and collections</p>
<NewsletterSignup />
</div>
</footer>
);
}

Every newsletter email should include an unsubscribe link:

<p>
No longer interested?
<a href="https://gallery.artbase.studio/unsubscribe?email={{email}}">
Unsubscribe
</a>
</p>

Database Schema

CREATE TABLE gallery_newsletter_subscribers (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
email TEXT UNIQUE NOT NULL,
first_name TEXT,
last_name TEXT,
is_active BOOLEAN DEFAULT TRUE,
subscribed_at TIMESTAMPTZ DEFAULT NOW(),
unsubscribed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX idx_newsletter_email ON gallery_newsletter_subscribers(email);
CREATE INDEX idx_newsletter_active ON gallery_newsletter_subscribers(is_active);

GDPR & Privacy Compliance

Data Stored

Newsletter subscription stores:

  • ✅ Email address
  • ✅ First and last name (optional)
  • ✅ Subscription timestamps
  • ✅ Active status

User Rights

  • Right to access: Users can request their subscription data
  • Right to deletion: Unsubscribe or request full deletion
  • Right to portability: Export subscription data
  • Right to correction: Update email or name
  • Explicit opt-in: Users must actively subscribe
  • Clear purpose: Explain what emails they'll receive
  • Easy opt-out: Unsubscribe link in every email

Privacy Policy

Include newsletter details in privacy policy:

Newsletter Subscriptions

When you subscribe to our newsletter, we collect:
- Email address (required)
- First and last name (optional)

We use this information to send periodic updates about:
- New artists and collections
- Gallery features and news
- Special events and sales

You can unsubscribe anytime using the link in any email.
We will never sell or share your email address with third parties.

Email Service Integration

The Newsletter API stores subscriptions but doesn't send emails directly. Integrate with an email service:

ServicePricingFeatures
SendGridFree tier: 100/dayTemplates, analytics
MailchimpFree tier: 500 subscribersCampaigns, automation
AWS SES$0.10/1000 emailsScalable, low cost
ConvertKitStarts at $29/moCreator-focused, landing pages

Integration Example (SendGrid)

import sgMail from '@sendgrid/mail';

sgMail.setApiKey(process.env.SENDGRID_API_KEY);

export async function sendNewsletterEmail(
subscribers: Array<{ email: string; firstName?: string }>,
subject: string,
htmlContent: string
) {
const messages = subscribers
.filter(sub => sub.is_active)
.map(sub => ({
to: sub.email,
from: 'newsletter@artbase.studio',
subject: subject,
html: htmlContent.replace('{{firstName}}', sub.firstName || 'Friend'),
}));

await sgMail.send(messages);
}

Sending a Campaign

// Fetch active subscribers
const { data: subscribers } = await supabase
.from('gallery_newsletter_subscribers')
.select('email, first_name')
.eq('is_active', true);

// Send email
await sendNewsletterEmail(
subscribers,
'New Artists This Month',
newsletterTemplate
);

Testing

Test Subscription

# Subscribe
curl -X POST http://localhost:3000/api/gallery/newsletter/subscribe \
-H "Content-Type: application/json" \
-d '{
"email": "test@example.com",
"firstName": "Test",
"lastName": "User"
}'

# Verify in database
# SELECT * FROM gallery_newsletter_subscribers WHERE email = 'test@example.com';

# Unsubscribe
curl -X POST http://localhost:3000/api/gallery/newsletter/unsubscribe \
-H "Content-Type: application/json" \
-d '{"email": "test@example.com"}'

# Verify is_active = false

Next Steps