npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@cslegany/strapi5-sendgrid-tools

v0.0.1

Published

SendGrid sync integration with Strapi5

Downloads

54

Readme

sendgrid-tools

Use it with the following entity in your strapi project: subscription.

schema.json: { "kind": "collectionType", "collectionName": "subscriptions", "info": { "singularName": "subscription", "pluralName": "subscriptions", "displayName": "Subscription", "description": "" }, "options": { "draftAndPublish": false }, "pluginOptions": {}, "attributes": { "title": { "type": "string" }, "email": { "type": "email", "required": true, "unique": true }, "first_name": { "type": "string", "required": true }, "last_name": { "type": "string", "required": true }, "subscription_status": { "type": "enumeration", "enum": [ "subscribed", "unsubscribed" ], "default": "subscribed", "required": true }, "unsubscribed_at": { "type": "datetime" }, "resubscribed_at": { "type": "datetime" }, "unsubscribe_token": { "type": "string", "unique": true } } }

controllers/subscription.ts: import { factories } from '@strapi/strapi';

export default factories.createCoreController('api::subscription.subscription', ({ strapi }) => ({ async unsubscribe(ctx) { const token = ctx.request.body?.token;

	if (!token) {
		return ctx.badRequest('Missing token');
	}

	const result = await strapi
		.service('api::subscription.subscription')
		.unsubscribeByToken(token);

	if (!result.ok && result.code === 'invalid-token') {
		return ctx.notFound('Invalid token');
	}

	return ctx.send(result);
},

async resyncSendgrid(ctx) {
	const internalToken = ctx.request.header.authorization?.replace('Bearer ', '');
	const expectedToken = process.env.INTERNAL_SYNC_TOKEN;

	if (!expectedToken || internalToken !== expectedToken) {
		return ctx.unauthorized('Invalid token');
	}

	const result = await strapi
		.service('api::subscription.subscription')
		.resyncAllSubscribedToSendgrid();

	return ctx.send(result);
},

async getSendgridCustomFields(ctx) {
	const internalToken = ctx.request.header.authorization?.replace('Bearer ', '');
	const expectedToken = process.env.INTERNAL_SYNC_TOKEN;
	const isDev = process.env.NODE_ENV === 'development';
	const apiKey = process.env.SENDGRID_API_KEY;

	if (!isDev && (!expectedToken || internalToken !== expectedToken)) {
		return ctx.unauthorized('Invalid token');
	}

	if (!apiKey) {
		return ctx.internalServerError('Missing SENDGRID_API_KEY');
	}

	const res = await fetch('https://api.sendgrid.com/v3/marketing/field_definitions', {
		method: 'GET',
		headers: {
			Authorization: `Bearer ${apiKey}`,
			'Content-Type': 'application/json',
		},
	});

	if (!res.ok) {
		const text = await res.text();
		strapi.log.error(`SendGrid field definitions failed: ${res.status} ${text}`);
		return ctx.internalServerError(`SendGrid field definitions failed: ${res.status}`);
	}

	const json = await res.json();
	return ctx.send(json);
},

}));

routes/subscription.ts: export default { routes: [ { method: 'POST', path: '/subscriptions/resync-sendgrid', handler: 'subscription.resyncSendgrid', config: { auth: false, }, }, { method: 'GET', path: '/subscriptions/sendgrid-custom-fields', handler: 'subscription.getSendgridCustomFields', config: { auth: false, }, }, { method: 'POST', path: '/subscriptions/unsubscribe', handler: 'subscription.unsubscribe', config: { auth: false, }, }, ], };

services/subscription.ts: import { factories } from '@strapi/strapi'; import crypto from 'crypto';

type SendgridToolsSettings = { enabled: boolean; syncUnsubscribed: boolean; };

async function getSendgridToolsSettings(strapi: any): Promise { const settings = await strapi .plugin('sendgrid-tools') .service('settings') .getSettings();

return {
	enabled: settings?.enabled ?? true,
	syncUnsubscribed: settings?.syncUnsubscribed ?? false,
};

}

type SubscriptionInput = { email: string; first_name: string; last_name: string; };

type SendgridContactInput = { email: string; first_name: string; last_name: string; unsubscribe_url: string; };

type SendgridSuppressionMode = | { type: 'none' } | { type: 'global' } | { type: 'group'; groupId: string };

type SubscriptionStatus = 'subscribed' | 'unsubscribed';

function normalizeInput(data: SubscriptionInput): SubscriptionInput { return { email: data.email.trim().toLowerCase(), first_name: data.first_name.trim(), last_name: data.last_name.trim(), }; }

function buildTitle(data: SubscriptionInput) { return ${data.last_name.trim()} ${data.first_name.trim()} <${data.email.trim().toLowerCase()}>; }

function buildUnsubscribeUrl(token: string) { const frontendUrl = process.env.FRONTEND_URL;

if (!frontendUrl) {
	throw new Error('Missing FRONTEND_URL');
}

return `${frontendUrl.replace(/\/$/, '')}/hirlevel/leiratkozas?token=${encodeURIComponent(token)}`;

}

function getErrorDetails(err: unknown) { if (err instanceof Error) { const cause = (err as Error & { cause?: unknown }).cause;

	if (cause instanceof Error) {
		return `${err.name}: ${err.message} | cause: ${cause.name}: ${cause.message}`;
	}

	if (cause) {
		try {
			return `${err.name}: ${err.message} | cause: ${JSON.stringify(cause)}`;
		} catch {
			return `${err.name}: ${err.message} | cause: ${String(cause)}`;
		}
	}

	return `${err.name}: ${err.message}`;
}

try {
	return JSON.stringify(err);
} catch {
	return String(err);
}

}

function sleep(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)); }

async function fetchWithTimeout(url: string, options: RequestInit = {}, timeoutMs = 10000) { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), timeoutMs);

try {
	return await fetch(url, {
		...options,
		signal: controller.signal,
	});
} finally {
	clearTimeout(timeout);
}

}

async function fetchWithRetry( url: string, options: RequestInit, { retries = 3, timeoutMs = 10000, }: { retries?: number; timeoutMs?: number } = {} ) { let lastError: unknown;

for (let attempt = 1; attempt <= retries; attempt++) {
	try {
		const res = await fetchWithTimeout(url, options, timeoutMs);

		if (res.ok) {
			return res;
		}

		if ([408, 429, 500, 502, 503, 504].includes(res.status) && attempt < retries) {
			await sleep(attempt * 1000);
			continue;
		}

		return res;
	} catch (err) {
		lastError = err;

		if (attempt < retries) {
			await sleep(attempt * 1000);
			continue;
		}
	}
}

throw lastError;

}

function getSendgridSuppressionMode(): SendgridSuppressionMode { const groupId = process.env.SENDGRID_NEWSLETTER_SUPPRESSION_GROUP_ID?.trim(); const useGlobal = process.env.SENDGRID_USE_GLOBAL_SUPPRESSION?.trim().toLowerCase() === 'true';

if (groupId) {
	return { type: 'group', groupId };
}

if (useGlobal) {
	return { type: 'global' };
}

return { type: 'none' };

}

function getRequiredSendgridApiKey() { const apiKey = process.env.SENDGRID_API_KEY;

if (!apiKey) {
	throw new Error('Missing SENDGRID_API_KEY');
}

return apiKey;

}

function getRequiredUnsubscribeUrlFieldId() { const unsubscribeUrlFieldId = process.env.SENDGRID_UNSUBSCRIBE_URL_FIELD_ID;

if (!unsubscribeUrlFieldId) {
	throw new Error('Missing SENDGRID_UNSUBSCRIBE_URL_FIELD_ID');
}

return unsubscribeUrlFieldId;

}

function getOptionalSendgridListId() { return process.env.SENDGRID_NEWSLETTER_LIST_ID?.trim() || null; }

async function lookupSendgridContactIdByEmail(email: string): Promise<string | null> { const apiKey = getRequiredSendgridApiKey(); const normalizedEmail = email.trim().toLowerCase();

const res = await fetchWithRetry(
	'https://api.sendgrid.com/v3/marketing/contacts/search/emails',
	{
		method: 'POST',
		headers: {
			Authorization: `Bearer ${apiKey}`,
			'Content-Type': 'application/json',
		},
		body: JSON.stringify({
			emails: [normalizedEmail],
		}),
	},
	{ retries: 3, timeoutMs: 10000 }
);

if (res.status === 404) {
	return null;
}

if (!res.ok) {
	const text = await res.text();
	throw new Error(`SendGrid contact lookup failed: ${res.status} ${res.statusText} ${text}`);
}

const json = (await res.json()) as {
	result?: Record<string, { contact?: { id?: string } }>;
};

return json?.result?.[normalizedEmail]?.contact?.id ?? null;

}

async function removeSendgridContactFromListById(contactId: string) { const apiKey = getRequiredSendgridApiKey(); const listId = getOptionalSendgridListId();

if (!listId) {
	return { removed: false, reason: 'missing_list_id' as const };
}

const removeUrl =
	`https://api.sendgrid.com/v3/marketing/lists/${encodeURIComponent(listId)}/contacts` +
	`?contact_ids=${encodeURIComponent(contactId)}`;

// strapi.log.info('[SendGrid remove from list] url:', removeUrl);

const res = await fetchWithRetry(
	removeUrl,
	{
		method: 'DELETE',
		headers: {
			Authorization: `Bearer ${apiKey}`,
		},
	},
	{ retries: 3, timeoutMs: 10000 }
);

// strapi.log.info('[SendGrid remove from list] status:', res.status);

if (!res.ok) {
	const text = await res.text();
	throw new Error(`SendGrid remove from list failed: ${res.status} ${res.statusText} ${text}`);
}

return { removed: true, listId, contactId };

}

async function addEmailToGlobalSuppression(email: string) { const apiKey = getRequiredSendgridApiKey(); const normalizedEmail = email.trim().toLowerCase();

const res = await fetchWithRetry(
	'https://api.sendgrid.com/v3/asm/suppressions/global',
	{
		method: 'POST',
		headers: {
			Authorization: `Bearer ${apiKey}`,
			'Content-Type': 'application/json',
		},
		body: JSON.stringify({
			recipient_emails: [normalizedEmail],
		}),
	},
	{ retries: 3, timeoutMs: 10000 }
);

if (!res.ok) {
	const text = await res.text();
	throw new Error(`SendGrid add global suppression failed: ${res.status} ${res.statusText} ${text}`);
}

return { suppressed: true, type: 'global' as const, email: normalizedEmail };

}

async function removeEmailFromGlobalSuppression(email: string) { const apiKey = getRequiredSendgridApiKey(); const normalizedEmail = email.trim().toLowerCase();

const res = await fetchWithRetry(
	`https://api.sendgrid.com/v3/asm/suppressions/global/${encodeURIComponent(normalizedEmail)}`,
	{
		method: 'DELETE',
		headers: {
			Authorization: `Bearer ${apiKey}`,
		},
	},
	{ retries: 3, timeoutMs: 10000 }
);

if (res.status === 404) {
	return {
		unsuppressed: false,
		reason: 'not_found' as const,
		type: 'global' as const,
		email: normalizedEmail,
	};
}

if (!res.ok) {
	const text = await res.text();
	throw new Error(`SendGrid remove global suppression failed: ${res.status} ${res.statusText} ${text}`);
}

return { unsuppressed: true, type: 'global' as const, email: normalizedEmail };

}

async function addEmailToSuppressionGroup(email: string, groupId: string) { const apiKey = getRequiredSendgridApiKey(); const normalizedEmail = email.trim().toLowerCase();

const res = await fetchWithRetry(
	`https://api.sendgrid.com/v3/asm/groups/${encodeURIComponent(groupId)}/suppressions`,
	{
		method: 'POST',
		headers: {
			Authorization: `Bearer ${apiKey}`,
			'Content-Type': 'application/json',
		},
		body: JSON.stringify({
			recipient_emails: [normalizedEmail],
		}),
	},
	{ retries: 3, timeoutMs: 10000 }
);

if (!res.ok) {
	const text = await res.text();
	throw new Error(`SendGrid add group suppression failed: ${res.status} ${res.statusText} ${text}`);
}

return { suppressed: true, type: 'group' as const, groupId, email: normalizedEmail };

}

async function removeEmailFromSuppressionGroup(email: string, groupId: string) { const apiKey = getRequiredSendgridApiKey(); const normalizedEmail = email.trim().toLowerCase();

const res = await fetchWithRetry(
	`https://api.sendgrid.com/v3/asm/groups/${encodeURIComponent(groupId)}/suppressions/${encodeURIComponent(normalizedEmail)}`,
	{
		method: 'DELETE',
		headers: {
			Authorization: `Bearer ${apiKey}`,
		},
	},
	{ retries: 3, timeoutMs: 10000 }
);

if (res.status === 404) {
	return {
		unsuppressed: false,
		reason: 'not_found' as const,
		type: 'group' as const,
		groupId,
		email: normalizedEmail,
	};
}

if (!res.ok) {
	const text = await res.text();
	throw new Error(`SendGrid remove group suppression failed: ${res.status} ${res.statusText} ${text}`);
}

return { unsuppressed: true, type: 'group' as const, groupId, email: normalizedEmail };

}

async function resubscribeSendgridContactByEmail(email: string) { const normalizedEmail = email.trim().toLowerCase(); const suppressionMode = getSendgridSuppressionMode();

if (suppressionMode.type === 'none') {
	return {
		ok: true,
		email: normalizedEmail,
		suppression: { removed: false, reason: 'disabled' as const },
	};
}

if (suppressionMode.type === 'global') {
	const result = await removeEmailFromGlobalSuppression(normalizedEmail);

	return {
		ok: true,
		email: normalizedEmail,
		suppression: result,
	};
}

const result = await removeEmailFromSuppressionGroup(normalizedEmail, suppressionMode.groupId);

return {
	ok: true,
	email: normalizedEmail,
	suppression: result,
};

}

async function unsubscribeSendgridContactByEmail(email: string) { const normalizedEmail = email.trim().toLowerCase(); const suppressionMode = getSendgridSuppressionMode();

const contactId = await lookupSendgridContactIdByEmail(normalizedEmail);

// strapi.log.info('[SendGrid unsubscribe] email:', normalizedEmail);
// strapi.log.info('[SendGrid unsubscribe] contactId:', contactId);
// strapi.log.info('[SendGrid unsubscribe] suppressionMode:', suppressionMode);
// strapi.log.info(`[SendGrid lookup] email=${normalizedEmail} contactId=${contactId ?? 'null'}`);

let listResult:
	| { removed: boolean; listId: string; contactId: string }
	| { removed: boolean; reason: 'not_found' | 'missing_list_id' };

if (!contactId) {
	listResult = { removed: false, reason: 'not_found' };
} else {
	listResult = await removeSendgridContactFromListById(contactId);
}

// strapi.log.info('[SendGrid unsubscribe] listResult:', listResult);

let suppressionResult:
	| { applied: false; reason: 'disabled' }
	| { applied: true; type: 'global' | 'group'; email: string; groupId?: string };

if (suppressionMode.type === 'none') {
	suppressionResult = { applied: false, reason: 'disabled' };
} else if (suppressionMode.type === 'global') {
	await addEmailToGlobalSuppression(normalizedEmail);
	suppressionResult = {
		applied: true,
		type: 'global',
		email: normalizedEmail,
	};
} else {
	await addEmailToSuppressionGroup(normalizedEmail, suppressionMode.groupId);
	suppressionResult = {
		applied: true,
		type: 'group',
		groupId: suppressionMode.groupId,
		email: normalizedEmail,
	};
}

// strapi.log.info('[SendGrid unsubscribe] suppressionResult:', suppressionResult);

return {
	ok: true,
	email: normalizedEmail,
	list: listResult,
	suppression: suppressionResult,
};

}

async function upsertSendgridContact(data: SendgridContactInput) { const apiKey = getRequiredSendgridApiKey(); const listId = getOptionalSendgridListId(); const unsubscribeUrlFieldId = getRequiredUnsubscribeUrlFieldId();

const payload: Record<string, unknown> = {
	contacts: [
		{
			email: data.email.trim().toLowerCase(),
			first_name: data.first_name.trim(),
			last_name: data.last_name.trim(),
			custom_fields: {
				[unsubscribeUrlFieldId]: data.unsubscribe_url,
			},
		},
	],
};

if (listId) {
	payload.list_ids = [listId];
}

let res: Response;

try {
	res = await fetchWithRetry(
		'https://api.sendgrid.com/v3/marketing/contacts',
		{
			method: 'PUT',
			headers: {
				Authorization: `Bearer ${apiKey}`,
				'Content-Type': 'application/json',
			},
			body: JSON.stringify(payload),
		},
		{ retries: 3, timeoutMs: 10000 }
	);
} catch (err) {
	throw new Error(`SendGrid contact upsert fetch failed: ${getErrorDetails(err)}`);
}

if (!res.ok) {
	const text = await res.text();
	throw new Error(`SendGrid contact upsert failed: ${res.status} ${res.statusText} ${text}`);
}

return res.json();

}

async function upsertSendgridContactBatch(contacts: SendgridContactInput[]) { const apiKey = getRequiredSendgridApiKey(); const listId = getOptionalSendgridListId(); const unsubscribeUrlFieldId = getRequiredUnsubscribeUrlFieldId();

const payload: Record<string, unknown> = {
	contacts: contacts.map((c) => ({
		email: c.email.trim().toLowerCase(),
		first_name: c.first_name.trim(),
		last_name: c.last_name.trim(),
		custom_fields: {
			[unsubscribeUrlFieldId]: c.unsubscribe_url,
		},
	})),
};

if (listId) {
	payload.list_ids = [listId];
}

const res = await fetchWithRetry(
	'https://api.sendgrid.com/v3/marketing/contacts',
	{
		method: 'PUT',
		headers: {
			Authorization: `Bearer ${apiKey}`,
			'Content-Type': 'application/json',
		},
		body: JSON.stringify(payload),
	},
	{ retries: 3, timeoutMs: 10000 }
);

if (!res.ok) {
	const text = await res.text();
	throw new Error(`SendGrid batch upsert failed: ${res.status} ${res.statusText} ${text}`);
}

return res.json();

}

async function ensureUnsubscribeToken(strapi: any, row: any): Promise { if (row.unsubscribe_token) { return row.unsubscribe_token; }

const newToken = crypto.randomUUID();

await strapi.documents('api::subscription.subscription').update({
	documentId: row.documentId,
	data: {
		unsubscribe_token: newToken,
	},
});

// strapi.log.info(`Generated missing unsubscribe_token for subscription ${row.documentId}`);

return newToken;

}

async function buildSendgridContactFromSubscription(strapi: any, row: any): Promise { const unsubscribeToken = await ensureUnsubscribeToken(strapi, row);

return {
	email: row.email.trim().toLowerCase(),
	first_name: row.first_name?.trim() ?? '',
	last_name: row.last_name?.trim() ?? '',
	unsubscribe_url: buildUnsubscribeUrl(unsubscribeToken),
};

}

async function syncSubscriptionRowToSendgrid(strapi: any, row: any) { const email = row.email.trim().toLowerCase();

if (row.subscription_status === 'subscribed') {
	const contact = await buildSendgridContactFromSubscription(strapi, row);
	await resubscribeSendgridContactByEmail(contact.email);
	await upsertSendgridContact(contact);

	return {
		ok: true,
		code: 'resynced-subscribed',
		email: contact.email,
		subscription_status: row.subscription_status,
	};
}

if (row.subscription_status === 'unsubscribed') {
	await unsubscribeSendgridContactByEmail(email);

	return {
		ok: true,
		code: 'resynced-unsubscribed',
		email,
		subscription_status: row.subscription_status,
	};
}

return {
	ok: false,
	code: 'unsupported-status',
	email,
	subscription_status: row.subscription_status,
};

}

export default factories.createCoreService('api::subscription.subscription', ({ strapi }) => ({ async create(params) { const raw = params?.data as SubscriptionInput; const data = normalizeInput(raw);

	const existing = await strapi.documents('api::subscription.subscription').findMany({
		filters: { email: data.email },
		limit: 1,
	});

	const found = existing[0];
	const title = buildTitle(data);

	if (!found) {
		const unsubscribeToken = crypto.randomUUID();
		const unsubscribeUrl = buildUnsubscribeUrl(unsubscribeToken);

		const result = await super.create({
			...params,
			data: {
				...params.data,
				...data,
				title,
				subscription_status: 'subscribed',
				unsubscribe_token: unsubscribeToken,
				resubscribed_at: new Date().toISOString(),
			},
		});

		try {
			await resubscribeSendgridContactByEmail(data.email);

			await upsertSendgridContact({
				...data,
				unsubscribe_url: unsubscribeUrl,
			});
		} catch (err) {
			if (err instanceof Error) {
				strapi.log.error(`SendGrid sync error on create: ${getErrorDetails(err)}`);
			} else {
				strapi.log.error(`SendGrid sync error on create: ${JSON.stringify(err)}`);
			}
		}

		return result;
	}

	if (found.subscription_status === 'subscribed') {
		return {
			...found,
			subscriptionState: 'already-subscribed',
		};
	}

	const newToken = crypto.randomUUID();
	const unsubscribeUrl = buildUnsubscribeUrl(newToken);

	const updated = await strapi.documents('api::subscription.subscription').update({
		documentId: found.documentId,
		data: {
			title,
			first_name: data.first_name,
			last_name: data.last_name,
			subscription_status: 'subscribed',
			unsubscribed_at: null,
			resubscribed_at: new Date().toISOString(),
			unsubscribe_token: newToken,
		},
	});

	try {
		await resubscribeSendgridContactByEmail(data.email);

		await upsertSendgridContact({
			...data,
			unsubscribe_url: unsubscribeUrl,
		});
	} catch (err) {
		if (err instanceof Error) {
			strapi.log.error(`SendGrid sync error on resubscribe: ${getErrorDetails(err)}`);
		} else {
			strapi.log.error(`SendGrid sync error on resubscribe: ${JSON.stringify(err)}`);
		}
	}

	return {
		...updated,
		subscriptionState: 'resubscribed',
	};
},

async unsubscribeByToken(token: string) {
	const entries = await strapi.documents('api::subscription.subscription').findMany({
		filters: { unsubscribe_token: token },
		limit: 1,
	});

	const found = entries[0];

	if (!found) {
		return { ok: false, code: 'invalid-token' };
	}

	if (found.subscription_status === 'unsubscribed') {
		return { ok: true, code: 'already-unsubscribed' };
	}

	const updated = await strapi.documents('api::subscription.subscription').update({
		documentId: found.documentId,
		data: {
			subscription_status: 'unsubscribed',
			unsubscribed_at: new Date().toISOString(),
		},
	});

	try {
		// strapi.log.info(`[unsubscribeByToken] calling SendGrid unsubscribe for: ${found.email}`);
		const sgResult = await unsubscribeSendgridContactByEmail(found.email);
		// strapi.log.info(`[unsubscribeByToken] SendGrid result: ${JSON.stringify(sgResult)}`);
	} catch (err) {
		if (err instanceof Error) {
			strapi.log.error(`SendGrid sync error on unsubscribe: ${getErrorDetails(err)}`);
		} else {
			strapi.log.error(`SendGrid sync error on unsubscribe: ${JSON.stringify(err)}`);
		}
	}

	return { ok: true, code: 'unsubscribed', data: updated };
},

async resyncAllSubscribedToSendgrid() {
	const pluginSettings = await getSendgridToolsSettings(strapi);
	const syncUnsubscribed = pluginSettings.syncUnsubscribed;

	const pageSize = 500;
	let start = 0;
	let totalSynced = 0;

	const allowedStatuses: SubscriptionStatus[] = syncUnsubscribed
		? ['subscribed', 'unsubscribed']
		: ['subscribed'];

	while (true) {
		const rows = await strapi.documents('api::subscription.subscription').findMany({
			filters: {
				subscription_status: {
					$in: allowedStatuses,
				},
			},
			sort: { email: 'asc' },
			start,
			limit: pageSize,
		});

		if (!rows.length) {
			break;
		}

		try {
			const subscribedRows = rows.filter((row: any) => row.subscription_status === 'subscribed');
			const unsubscribedRows = rows.filter((row: any) => row.subscription_status === 'unsubscribed');

			if (subscribedRows.length > 0) {
				const contacts = await Promise.all(
					subscribedRows.map((row: any) => buildSendgridContactFromSubscription(strapi, row))
				);

				for (const contact of contacts) {
					await resubscribeSendgridContactByEmail(contact.email);
				}

				await upsertSendgridContactBatch(contacts);
				totalSynced += contacts.length;
			}

			if (syncUnsubscribed && unsubscribedRows.length > 0) {
				for (const row of unsubscribedRows) {
					await unsubscribeSendgridContactByEmail(row.email);
					totalSynced += 1;
				}
			}
		} catch (err) {
			if (err instanceof Error) {
				strapi.log.error(`SendGrid batch sync error at start ${start}: ${getErrorDetails(err)}`);
			} else {
				strapi.log.error(`SendGrid batch sync error at start ${start}: ${JSON.stringify(err)}`);
			}
			throw err;
		}

		if (rows.length < pageSize) {
			break;
		}

		start += pageSize;
	}

	return { ok: true, totalSynced };
},

async resyncOneSubscriptionToSendgrid(documentId: string) {
	const row = await strapi.documents('api::subscription.subscription').findOne({
		documentId,
	});

	if (!row) {
		return { ok: false, code: 'not-found' };
	}

	const pluginSettings = await getSendgridToolsSettings(strapi);

	if (row.subscription_status === 'subscribed') {
		const contact = await buildSendgridContactFromSubscription(strapi, row);

		await resubscribeSendgridContactByEmail(contact.email);
		await upsertSendgridContact(contact);

		return {
			ok: true,
			code: 'resynced',
			documentId,
			email: contact.email,
			subscription_status: row.subscription_status,
		};
	}

	if (row.subscription_status === 'unsubscribed') {
		if (!pluginSettings.syncUnsubscribed) {
			return {
				ok: false,
				code: 'not-subscribed',
				message: 'Csak subscribed állapotú rekord szinkronizálható.',
			};
		}

		await unsubscribeSendgridContactByEmail(row.email);

		return {
			ok: true,
			code: 'resynced',
			documentId,
			email: row.email.trim().toLowerCase(),
			subscription_status: row.subscription_status,
		};
	}

	return {
		ok: false,
		code: 'not-supported-status',
		message: `Nem támogatott subscription_status: ${row.subscription_status}`,
	};
},

}));