Send email from Contentful without exposing an email API key by using Contentful webhooks as the trigger and a server-side route as the delivery layer. This guide shows a practical, retry-safe way to turn a published Contentful entry into a transactional email sent through Volanea.
Contentful does not need a native Volanea app or marketplace plugin for this workflow. Its built-in webhooks can make an HTTP request whenever content changes, and a small endpoint you control can validate that request, map Contentful fields into an email payload, and call Volanea’s REST API.
What this Contentful email integration does
The pattern is deliberately simple:
- An editor creates or updates an entry in Contentful.
- The editor publishes that entry.
- Contentful fires an
Entry.publishwebhook for one specific content type. - Your server-side endpoint receives the Contentful entry JSON.
- The endpoint validates the webhook, maps the entry fields, and calls
POST https://api.volanea.com/v1/send. - Volanea accepts the message for delivery from a verified sending domain.
The concrete Contentful trigger in this guide is publishing an entry. In Contentful webhook terms, that is the Entry.publish event; the request includes an X-Contentful-Topic header identifying the event as ContentManagement.Entry.publish.
This is a useful model when content editors need control over editorial notification emails, release notices, event reminders, partner updates, or a small number of one-to-one operational messages. It is not a replacement for a product’s real-time password reset, checkout receipt, or account-security flow. Those sends should normally originate in the application that owns the user action and transaction.
Why use a webhook and middleware route instead of a direct API call
Contentful webhooks support outbound HTTP POST requests, filtering, custom headers, and payload transformations. In theory, you can transform an entry payload directly into another service’s request format. In practice, a server-side middleware route is the more robust option for sending email.
The main reason is secret handling. Volanea authenticates requests with Authorization: Bearer <key>. A Volanea secret key should live in your deployment platform’s encrypted environment variables or secrets manager, not in browser code, a public Contentful delivery token, a frontend build variable, or any client-visible configuration.
A direct Contentful-to-Volanea webhook would require the Volanea API key to be saved in the webhook’s custom request headers. That is technically possible for a trusted administrative configuration, but it makes the Contentful webhook definition a location that must be protected, audited, and rotated like any other secret-bearing integration. It also makes conditional logic, duplicate prevention, validation, logging, and error handling much more limited.
With middleware, the division of responsibility is clearer:
- Contentful stores: the URL of your webhook receiver and, optionally, a separate shared webhook secret in a custom header.
- Your server stores:
VOLANEA_API_KEY, the verified sender address, and any infrastructure credentials. - Volanea receives: only a validated, normalized email request.
- Your application logs: the Contentful entry ID, revision, idempotency key, Volanea response, and any rejected messages.
This route also gives you a safe place to reject malformed content before it becomes an outbound email.
Create a purpose-built email entry content type
Do not trigger an email from every entry publish in a Contentful space. Broad webhook topics create accidental sends when editors publish pages, assets, navigation records, or unrelated content. Instead, create one narrowly defined content type, such as emailNotification.
A practical content model can include these fields:
| Field ID | Suggested type | Purpose |
|---|---|---|
to | Short text | Recipient email address |
subject | Short text | Email subject line |
html | Long text | HTML email body |
text | Long text | Plain-text fallback |
sendEmail | Boolean | Optional editorial guardrail |
messageType | Short text | Optional label such as release-notice |
Make to, subject, and at least one body field required. If the entry is localized, decide whether an email should use a fixed locale or the entry locale. A webhook payload reflects the entry’s field structure, including locale keys, so code must read the locale explicitly rather than assume fields are flat strings.
The sendEmail boolean is optional but helpful. It allows teams to publish a draft entry for review without necessarily making it eligible to send. Your endpoint can reject entries where the value is not exactly true.
For larger campaigns, do not use one Contentful entry per recipient. Contentful is the source of content, while recipient selection, unsubscribe state, suppression handling, batching, and campaign analytics should be handled in the email system or application layer. A better pattern is to publish one campaign-content entry and let a trusted backend decide who is eligible to receive it.
Configure the Contentful webhook trigger
In the Contentful web app, open Settings → Webhooks, choose Add webhook, and configure a public HTTPS endpoint that your application controls. Contentful requires a reachable HTTP or HTTPS host; localhost and private network addresses are not valid webhook destinations.
Use a webhook URL such as:
https://app.example.com/api/contentful/email-notification
Choose the Entry.publish topic. Then filter the webhook to your email content type ID, for example emailNotification. Contentful supports filters based on properties in the webhook payload, including an entry’s content type and environment. This is important: a production email webhook should subscribe to the smallest possible set of events and entries.
Also filter by environment if your space has development, staging, and production environments. You do not want an editor testing a notification in a staging environment to send a real email to a customer.
Add a custom header that carries a separate shared value for authenticating the Contentful-to-your-server hop:
X-Contentful-Webhook-Secret: a-long-random-value
Store the same value as CONTENTFUL_WEBHOOK_SECRET in your server environment. This shared value is not your Volanea key. Keeping those credentials separate limits the impact of a leak and lets you rotate either connection independently.
Contentful includes useful predefined headers, including X-Contentful-Topic, X-Contentful-Webhook-Name, and a Contentful management JSON content type. Your endpoint should inspect the topic header and only process ContentManagement.Entry.publish for this route.
Understand the Contentful webhook payload shape
For entry events, Contentful sends the entry as an entity JSON object. The exact sys metadata varies by space, environment, locale configuration, and event context, but an entry payload follows this general shape:
{
"metadata": {
"tags": []
},
"sys": {
"type": "Entry",
"id": "4h9fPcMZyQq7M2kD0Vx8Aa",
"createdAt": "2026-10-02T14:12:00.000Z",
"updatedAt": "2026-10-02T14:18:00.000Z",
"revision": 7,
"locale": "en-US",
"contentType": {
"sys": {
"type": "Link",
"linkType": "ContentType",
"id": "emailNotification"
}
},
"environment": {
"sys": {
"type": "Link",
"linkType": "Environment",
"id": "master"
}
}
},
"fields": {
"to": {
"en-US": "reader@example.com"
},
"subject": {
"en-US": "Your October product update"
},
"html": {
"en-US": "<h1>October update</h1><p>Your new content is ready.</p>"
},
"text": {
"en-US": "October update\n\nYour new content is ready."
},
"sendEmail": {
"en-US": true
}
}
}
Two details matter here. First, fields are keyed by locale, so fields.subject is not the subject string itself. It is an object such as { "en-US": "Your subject" }. Second, sys.revision changes as an entry changes. Combining the entry ID and revision provides a stable idempotency key for one published revision.
Do not assume every field exists. An editor may have saved incomplete data before validation changed, a field may not be required in every locale, or the content model may change later. Treat missing recipient, subject, and body values as a controlled validation failure rather than passing undefined values to the email API.
Working code: map a Contentful entry to a Volanea email
The following TypeScript example is a Next.js App Router route handler, but the same logic works in Express, Fastify, Cloudflare Workers, AWS Lambda, or any server environment with fetch. It receives the real Contentful entry-shaped JSON, validates the custom webhook secret, reads localized fields, and sends the mapped message through Volanea.
// app/api/contentful/email-notification/route.ts
import { NextRequest, NextResponse } from "next/server";
const CONTENTFUL_TOPIC = "ContentManagement.Entry.publish";
const EMAIL_CONTENT_TYPE = "emailNotification";
const LOCALE = "en-US";
type Localized<T> = Record<string, T>;
type ContentfulEntry = {
sys?: {
id?: string;
revision?: number;
locale?: string;
contentType?: {
sys?: { id?: string };
};
};
fields?: {
to?: Localized<string>;
subject?: Localized<string>;
html?: Localized<string>;
text?: Localized<string>;
sendEmail?: Localized<boolean>;
};
};
function valueForLocale<T>(field: Localized<T> | undefined, locale: string): T | undefined {
if (!field) return undefined;
return field[locale] ?? field[Object.keys(field)[0]];
}
function normalizeEmail(value: string): string {
return value.trim().toLowerCase();
}
export async function POST(request: NextRequest) {
const webhookSecret = request.headers.get("x-contentful-webhook-secret");
const topic = request.headers.get("x-contentful-topic");
if (webhookSecret !== process.env.CONTENTFUL_WEBHOOK_SECRET) {
return NextResponse.json({ error: "Unauthorized webhook" }, { status: 401 });
}
if (topic !== CONTENTFUL_TOPIC) {
return NextResponse.json({ ignored: true, reason: "Unexpected webhook topic" }, { status: 200 });
}
const entry = (await request.json()) as ContentfulEntry;
const contentTypeId = entry.sys?.contentType?.sys?.id;
if (contentTypeId !== EMAIL_CONTENT_TYPE) {
return NextResponse.json({ ignored: true, reason: "Unexpected content type" }, { status: 200 });
}
const locale = entry.sys?.locale ?? LOCALE;
const shouldSend = valueForLocale(entry.fields?.sendEmail, locale);
const to = valueForLocale(entry.fields?.to, locale);
const subject = valueForLocale(entry.fields?.subject, locale);
const html = valueForLocale(entry.fields?.html, locale);
const text = valueForLocale(entry.fields?.text, locale);
if (shouldSend !== true) {
return NextResponse.json({ ignored: true, reason: "sendEmail is not enabled" }, { status: 200 });
}
if (!to || !subject || (!html && !text)) {
return NextResponse.json(
{ error: "Entry is missing to, subject, or email body content" },
{ status: 422 }
);
}
const entryId = entry.sys?.id;
const revision = entry.sys?.revision;
if (!entryId || typeof revision !== "number") {
return NextResponse.json({ error: "Entry sys.id or sys.revision is missing" }, { status: 422 });
}
const volaneaResponse = await fetch("https://api.volanea.com/v1/send", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.VOLANEA_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": `contentful:${entryId}:revision:${revision}`
},
body: JSON.stringify({
from: process.env.VOLANEA_FROM,
to: normalizeEmail(to),
subject: subject.trim(),
html: html ?? undefined,
text: text ?? undefined
})
});
const responseBody = await volaneaResponse.text();
if (!volaneaResponse.ok) {
console.error("Volanea send failed", {
contentfulEntryId: entryId,
revision,
status: volaneaResponse.status,
responseBody
});
return NextResponse.json(
{ error: "Volanea rejected the email request" },
{ status: 502 }
);
}
console.info("Volanea email accepted", {
contentfulEntryId: entryId,
revision,
responseBody
});
return NextResponse.json({ accepted: true }, { status: 200 });
}
The mapping is explicit:
fields.to[locale]becomes Volanea’stovalue.fields.subject[locale]becomessubject.fields.html[locale]becomeshtml.fields.text[locale]becomestext.VOLANEA_FROMsupplies the verified sender address instead of allowing editors to choose arbitrary From addresses.sys.idplussys.revisionbecomes theIdempotency-Keyvalue.
Volanea’s send endpoint accepts a message request over JSON with Bearer authentication. Use a sender address on a domain you have verified in Volanea before testing production sends. For endpoint fields, API examples, and current response formats, use the Volanea email API reference.
Store credentials safely
This integration has two authentication boundaries, and they should use separate secrets.
Contentful to your endpoint
Store a long random CONTENTFUL_WEBHOOK_SECRET value in your deployment environment. Put the matching value in the Contentful webhook’s custom headers. The endpoint compares the received header with its environment variable before parsing the event as an email request.
This protects against simple unauthorized requests to a public webhook URL. It is not a replacement for normal application security: use HTTPS, avoid logging secrets, restrict who can edit webhook definitions, and rotate the header value if access changes.
Your endpoint to Volanea
Store VOLANEA_API_KEY in the server runtime’s secret manager or encrypted environment variable. Keep it out of:
- Client-side JavaScript bundles.
- Public
NEXT_PUBLIC_variables or equivalent frontend environment variables. - Contentful entries, rich-text fields, and JSON content.
- Git repositories, screenshots, support tickets, and browser local storage.
- A Contentful Delivery API response.
The API key has no legitimate role in a browser. If a frontend user can inspect it, they can use it to send email under your account. Your public website should submit a form to your own backend; Contentful should invoke your own webhook receiver; only that server should authenticate to Volanea.
If you choose a direct webhook transformation instead, the Volanea key would be configured as a custom Authorization header in the Contentful webhook definition. That keeps it out of public frontend traffic, but it still broadens the number of systems and administrators that can access a sending credential. The middleware design avoids that tradeoff.
Make publishes safe for editors and recipients
Publishing content is an editorial action, but sending email is an external side effect. Treat the integration as a production workflow with a release process.
Start with a non-production environment and a test recipient address you control. Publish a known-good email entry, inspect your endpoint logs, and confirm that Volanea accepted the request. Then check the mailbox for formatting, sender identity, plain-text fallback, and link correctness.
Use a fixed from address in the server configuration. Editors can author a reply-to address or friendly sender name only if your code validates those values and your domain policy permits them. Letting arbitrary content fields control sender identity can create deliverability, security, and brand-consistency problems.
It is also wise to keep a delivery audit record outside Contentful. At minimum, log:
- Contentful entry ID.
- Contentful revision number.
- Content type ID and environment.
- Recipient address after normalization or a safely redacted version.
- Idempotency key.
- Volanea HTTP status and response identifier.
- Timestamp and outcome.
This record lets an editor or support teammate answer a specific question later: “Did revision 7 of this notification actually get accepted for sending?”
When this breaks: retries, duplicates, timeouts, and missing data
The Contentful-to-email hop needs failure handling because Contentful webhooks are not a once-only message queue. Contentful’s default webhook retry policy retries 429 and 5xx responses up to two additional times, approximately 30 seconds apart. Contentful also recommends idempotent webhook consumers because duplicate deliveries can happen in rare failure conditions.
Contentful retries can create duplicate sends
A common failure sequence looks like this:
- Contentful sends the publish webhook.
- Your endpoint calls Volanea successfully.
- Your endpoint crashes or times out before returning a successful response to Contentful.
- Contentful retries the webhook.
- Your endpoint attempts to send the same email again.
The Idempotency-Key header prevents this from becoming two logical sends. Use a deterministic value based on the Contentful event identity. For this entry-based workflow, contentful:<entry-id>:revision:<revision> is a useful baseline because one entry revision maps to one send attempt.
Do not use a random UUID for this header. A random value changes on every retry, which defeats idempotency.
Webhook timeouts can be mistaken for send failures
A webhook endpoint should return quickly. Avoid doing long database work, rendering complex documents, fetching many linked entries, or waiting for unrelated services before acknowledging the Contentful request.
The example performs one outbound email API call, records its result, and returns. For more complex workflows, accept the webhook, persist an idempotent job, return a success response, and let a queue worker perform the email call. This design gives you longer retry windows and isolates slow provider responses from the Contentful request lifecycle.
If you add a queue, preserve the same deterministic idempotency key. Queue retries are another source of duplicate processing, not a reason to abandon duplicate protection.
Payload fields can be absent or localized differently
The webhook payload is based on your actual Contentful entry. Fields can be absent because they are optional, unpublished values may differ by locale, an editor used a locale you did not expect, or your content model changed after the endpoint was deployed.
Validate all required values at the receiver. Return a clear 422-style error in logs for missing to, subject, or body content. Do not silently substitute a production recipient, do not send an empty subject, and do not assume en-US is present if your space supports other locales.
Also avoid relying on optional contextual Contentful headers for core behavior. Contentful documents some event-context headers for scheduled actions, releases, and bulk actions, but notes that they are not available on all plans. Use the entry’s own sys and fields structure as the basis for sending; treat extra headers as useful diagnostics only.
A 200 response is not an inbox placement guarantee
A successful response from Volanea means the API accepted the request into the sending pipeline. It does not guarantee that the message reached the inbox. A recipient may be suppressed, unsubscribed, invalid, rejected by a receiving server, routed to spam, or blocked by a mailbox policy.
Configure Volanea event webhooks or review delivery events to distinguish accepted, delivered, bounced, complained, and suppressed outcomes. This is especially important when a Contentful entry triggers customer-facing communication: the editorial publish event answers “should we attempt to send?” while email events answer “what happened after we attempted it?”
Contentful webhook transformations: when direct delivery is reasonable
Contentful supports webhook transformations that can change the HTTP method, content type, request body, URL, and headers. It can resolve JSON pointers from the original webhook payload, making it possible to reshape an entry into a downstream API request.
That capability is useful for low-risk integrations where the target accepts a simple transformed payload and the authentication method is appropriate to store in the webhook configuration. For example, a content publish might notify an internal service that does not need complex validation.
For email sending, transformations are usually not enough on their own because production email logic benefits from:
- Recipient validation and normalization.
- Explicit content-type and environment checks.
- Locale fallback rules.
- Idempotency keys derived from the actual entry revision.
- Controlled sender identity.
- Audit logging and alerting.
- Retry classification.
- A place to implement consent and suppression business rules.
Use a direct transformation only after you have established that its secret storage, field mapping, duplicate behavior, and operational visibility meet your requirements. For most teams, a 50-line server route is easier to secure and maintain.
Deliverability considerations for Contentful-triggered mail
Contentful determines when content changes. It does not establish email sending reputation. Your Volanea configuration still needs an authenticated sending domain, accurate sender identity, sensible content, and an operational process for bounces and complaints.
Keep the following boundaries clear:
- Contentful: editorial content and the publish trigger.
- Your backend: authorization, mapping, validation, idempotency, and business rules.
- Volanea: message acceptance, sending infrastructure, suppression handling, and delivery events.
- Your source of truth for consent: the application or CRM where recipient permission is recorded.
Do not use a manually entered Contentful email field as proof that a person opted into marketing. If a message is promotional, obtain recipients from a consent-aware audience source and use campaign tooling designed for subscription management. A Contentful entry can supply the creative and copy without being the authority on eligibility.
For transactional-style notices, make the reason for sending clear and keep the recipient mapping narrow. A release note to an internal stakeholder is different from a product notice to thousands of customers. The larger the audience, the more important it becomes to separate content publishing from audience selection and to add an approval or scheduled-send process.
Before scaling, test sender authentication and message quality. Confirm that the From domain is verified, links use your intended domains, the plain-text version is readable, and preview text does not contain raw HTML. You can also use the email address verification tool before allowing externally supplied recipient data into any automated sending workflow.
Operational checklist before going live
Use this checklist before enabling the Contentful webhook in production:
- Create a dedicated Contentful content type rather than listening to all entry publishes.
- Filter the webhook by content type and production environment.
- Use HTTPS and a public webhook receiver URL.
- Add a separate custom webhook secret for Contentful-to-server authentication.
- Store
VOLANEA_API_KEYonly in server-side secrets. - Use a verified Volanea From domain and a fixed sender address.
- Validate recipient, subject, HTML, and plain-text content before sending.
- Add a deterministic
Idempotency-Keybased onsys.idandsys.revision. - Log entry ID, revision, response status, and message outcome.
- Test duplicate webhook delivery intentionally before production.
- Decide how editors should preview, approve, and roll back email entries.
- Monitor Volanea delivery, bounce, complaint, and suppression events after launch.
The workflow is successful when an editor can publish approved content without handling infrastructure secrets, while developers can trace every email request from a Contentful revision to an email API result.
Conclusion
To send email from Contentful with Volanea, use Contentful’s native Entry.publish webhook as the trigger and route that event through a server-side endpoint you control. The endpoint keeps the Volanea API key out of client-visible configuration, translates localized Contentful fields into a Volanea send request, and adds idempotency protection for webhook retries.
This approach does not depend on a native Contentful marketplace app. It uses the platform capability Contentful already provides—outbound webhooks—while keeping email credentials, validation, delivery logic, and operational controls in the place they belong: your backend.
FAQ
Can Contentful send an email directly to Volanea?
Contentful webhooks can make outbound HTTP requests and support payload and header transformations, so a direct request is possible in some cases. A server-side middleware route is usually safer because it keeps the Volanea API key in your own secrets manager and provides validation, logging, and duplicate prevention.
What Contentful event should trigger the email?
Use Entry.publish for an entry-based email workflow. Filter the webhook to a dedicated email content type and the production environment so unrelated entry publishes cannot trigger sends.
Where should the Volanea API key be stored?
Store it as a server-side secret, such as VOLANEA_API_KEY, in your deployment platform’s environment-variable or secret-management system. Do not expose it in frontend code, public Contentful content, browser configuration, or a client-accessible API response.
How do I stop duplicate emails when Contentful retries a webhook?
Send a deterministic Idempotency-Key with the Volanea request. A key built from the Contentful entry ID and revision, such as contentful:<entry-id>:revision:<revision>, makes repeated deliveries of the same published revision resolve to one logical email send.
Can I use this workflow for marketing campaigns?
Use Contentful to author campaign content, but do not use an entry’s manually entered email field as the audience source. Marketing sends need consent, unsubscribe handling, segmentation, suppression checks, and campaign-level controls managed by your application or email platform.