Send transactional Customer and Order emails from commercetools through an external email service provider.
Define transactional email requirements
Record each email as a separate requirement. Include its recipient, trigger, template, locale source and fallback, acceptable delivery delay, and tolerance for a missing or duplicate email. Also decide how long delivery records must remain available for support and audit purposes.
- Decide whether Customer creation sends an email for every Customer or only for Customers created through specific Stores.
- Define the token lifetime, delivery policy, and verification URL for email verification and password reset. Treat a missing token email as a blocking failure and protect the token as a secret.
- Decide which Order creation flows send a confirmation. Include imported and recurring Order creation when those flows are in scope.
- Allowlist the Order and shipment states that warrant a customer email.
- Describe return-information changes as return updates. Use an authoritative payment event when the email must state that money was refunded.
Record special requirements separately because they can change the application design:
- Define how multi-Store or multi-brand integrations select sender identities and templates.
- Define B2B recipients and any Business Unit-specific templates.
- Verify attachment support and size limits with the provider.
- Use a separate authenticated
serviceapplication when provider bounce or complaint webhooks must update commercetools. - Use a separate
jobapplication for batch or digest emails.
For each requirement, classify a duplicate and a missing email by business impact. This decision controls when the Connector acknowledges a Message and whether it needs durable deduplication.
Choose a Connector path
Understand the Connect application model
event application for a transactional email integration. A Subscription delivers a Message after the commercetools change occurs. The application then calls the email provider.Do not use an API Extension to send transactional email. An API Extension runs synchronously in a commercetools API request, so provider latency or an outage could delay or reject a Customer or Order operation.
Use an existing integration
For each candidate, verify the following before installation:
- It is deployable through Connect and supports your Region.
- Its contract includes the required Message types, templates, locale behavior, and provider.
- Its acknowledgment, retry, deduplication, and permanent-failure policies match your requirements.
- Its source, lifecycle behavior, security controls, tests, and operational runbook meet your production standards.
If configuration covers the requirements, use the existing Connector. This avoids maintaining a fork for a difference that a template mapping, state allowlist, or locale setting can resolve.
Create your own integration
If no existing Connector fits, choose one of these paths:
- Fork a source-available Connector when it already implements most of the required contract and has a specific code gap.
- Customize the transactional email integration template when you need its Subscription and routing scaffold.
- Build a new event application when the scaffold or an existing Connector would require broader changes than your own focused implementation.
sendMail method logs the sender, recipient, template ID, and template data instead. Treat the template as a starting point, not as production-ready software.Configure the Connector and email provider
event application, provider account, and Subscription as one contract.Configure the event application
CTP_REGION, in standard configuration. The application route must match the connect.yaml endpoint. For example, the template declares and serves /mailSender; a mismatch prevents delivery requests from reaching the handler.inheritAs.apiClient.scopes instead of supplying API Client credentials. Build the scope set from the enabled flows:| Capability | Scope |
|---|---|
Create or update the Subscription in postDeploy | manage_subscriptions |
| Retrieve an Order for an Order email | view_orders |
| Retrieve a Customer for a Customer email | view_customers |
| Create an email verification or password reset token in the handler | manage_customers, which replaces view_customers for that flow |
ConfigurationError health status and delivery stops after 24 hours on production Projects, or 1 hour on development and staging Projects. Keep the destination and Region configuration consistent with CTP_REGION, and monitor Subscription health rather than treating silence as success.Configure the email provider
Prefer templates that the provider hosts and that the Connector references by ID over rendering email markup in the Connector. Provider-hosted templates let non-technical teams change copy and layout without redeploying the Connector, and they keep the application a thin data-mapper. Render inside the Connector only when the provider offers no template feature or you require full control over the output.
Create the provider templates and define their required variables before implementing the mapping. Verify the sender address or domain with the provider. A provider can reject an unverified sender or accept the request without delivering the message to the inbox.
Manage the Subscription lifecycle
postDeploy and preUndeploy contract, and keep the destination and selected Message types synchronized with the deployed event application.ct-connect-email-delivery-subscription.postDeploy can leave a healthy application with no incoming Messages. Duplicate lifecycle registrations can cause duplicate sends.Map Customer and Order Messages
The Subscription filter and handler router must contain the same explicit set of Message types. Broad resource subscriptions increase cost and can route unrelated Messages into email logic.
Map each email requirement to its Subscription filter value:
resourceTypeId | Message type | |
|---|---|---|
| Customer registration | customer | CustomerCreated |
| Email verification | customer-email-token | CustomerEmailTokenCreated |
| Password reset | customer-password-token | CustomerPasswordTokenCreated |
| Order confirmation | order | OrderCreated, OrderImported, or OrderCreatedFromRecurringOrder |
| Order state or cancellation | order | OrderStateChanged |
| Shipment | order | OrderShipmentStateChanged |
| Return update | order | ReturnInfoAdded or ReturnInfoSet |
OrderCreatedFromRecurringOrderMessage. If recurring Order creation is in scope, add the Message type to both the Subscription filter and the handler router.payloadNotIncluded and failed lookups deliberately instead of building an incomplete email.Re-fetching also has a consequence for state emails. Because asynchronous Messages are not ordered, the retrieved Order can already have a later state than the transition recorded by the Message. Choose and test one policy:
- Use the transition represented by the Message to decide whether to send, then use the retrieved Order only for current template data.
- Use the current retrieved state and intentionally collapse intermediate transitions.
OrderStateChanged and OrderShipmentStateChanged. A handler that sends for every state Message can expose internal transitions and send too many emails. Map return-information Messages to return updates only. Use a separate, authoritative payment event if an email must claim that money was refunded.Implement the event handler
Process each delivery in this order:
- Decode the envelope for the configured destination and validate the notification type.
- Validate and route the Message type. Positively acknowledge irrelevant, valid Messages instead of retrying them.
- Establish the delivery record or deduplication key required by the selected delivery policy.
- Retrieve the Customer or Order when full or current data is required.
- Select the locale and template, then map the resource to the provider variables.
- Call the provider or durably enqueue the send.
- Persist the provider acceptance or terminal failure state before returning the final acknowledgment.
Keep the Message-to-provider mapping as a pure function. Test recipient and template selection, locale handling, money formatting, date formatting, optional fields, and state allowlists without calling commercetools or the provider.
The provider boundary must use the provider SDK or transactional API. Classify provider rate limits, timeouts, and server errors as transient. Classify invalid recipients, invalid templates, and rejected sender configuration as permanent according to the provider contract. Retry transient failures. For permanent failures, record the failure, alert the operator, and positively acknowledge or quarantine the event so it does not retry forever.
Choose delivery and deduplication behavior
102, 200, 201, 202, or 204 within 10 seconds. Any other response or a missed deadline can cause redelivery.event applications. The same 202 returned from an API Extension rejects the triggering operation, so do not carry acknowledgment logic across the two application types.The following diagrams compare the end-to-end flow for each policy. They show when the Connect application acknowledges the event, how failures affect delivery, and whether the Subscription can redeliver the Message.
| Policy | Flow | Benefit | Risk and required control |
|---|---|---|---|
| At-most-once | Acknowledge, then retrieve and send. | A Subscription redelivery cannot cause a second send. | A crash or provider failure after the acknowledgment silently drops the email. Use only when this loss is acceptable or when a separate durable queue owns retries. |
| At-least-once | Claim a durable key; send or durably persist the work; record the result; then acknowledge. | Transient failures can be retried. | Redelivery can duplicate the send. Deduplicate with the provider's idempotency key, or a shared, durable sent-marker when the provider has none. |
As a default, use at-most-once for Order and account confirmations, where a rare missed email is tolerable and a duplicate is merely unwelcome. Use at-least-once with deduplication for drop-intolerant emails, such as email verification and password reset, where a missing email blocks the Customer. Record the chosen policy for each email type.
202 before validation, routing, resource retrieval, and provider work. Later failures are logged but cannot cause Subscription redelivery.The audited template has no durable deduplication or provider-acceptance record, and it does not implement provider idempotency. Add these controls before changing the template to acknowledge after a send.
resource.id and sequenceNumber, so the provider itself collapses a duplicate send. When the provider has no idempotency feature, record a sent-marker that every instance shares, such as a Custom Object keyed on the same value. Re-check that marker against live state before sending. An in-memory set is lost on restart and does not coordinate concurrent instances, so it does not deduplicate.Design for an ambiguous provider result instead of assuming that every email is delivered exactly once. The provider can accept a request while the Connector times out before receiving the response. On retry, use the same provider idempotency key where supported, and reconcile an ambiguous result against provider activity before sending again.
Handle tokens, state transitions, localization, and personal data
Protect token flows
CustomerCreateEmailToken. For a replacement password token, use CustomerCreatePasswordResetToken. Never log token values.Preserve state meaning
Test the chosen transition policy with rapid consecutive Order updates. The email subject, body, and send condition must all refer to the same transition or intentionally selected current state. A retrieved current state must not silently change the meaning of an earlier Message.
Localize with a fallback
customer.locale or Store configuration. Define an ordered fallback for every localized value and provider template. The audited template hardcodes en-US for Order data and has no explicit locale fallback, so replace this behavior for a localized storefront.Keep logs safe
Log a correlation key, Message type, resource ID, attempt, duration, provider result category, and deduplication state. Do not log full Message bodies, recipient addresses, names, token values, or template data. The audited scaffold logs several of these values and must be changed before production use.
Verify and operate the integration
Before deployment, add focused automated tests for the following behavior:
- The route matches the
connect.yamlendpoint, and each supported destination envelope is decoded correctly. - Every selected Message type routes to the intended template; unselected types are acknowledged without a send.
- Resource retrieval handles omitted payloads, missing resources, and a resource that has advanced to a later state.
- State allowlists, locale selection, and fallback behavior are deterministic.
- The selected acknowledgment policy handles provider success, transient failure, permanent failure, timeout, and duplicate delivery.
- Deduplication survives process restart and concurrent handling.
- Token handling prevents self-trigger loops and repeated creation.
postDeploycreates or updates one Subscription, repeated deployment does not duplicate it, andpreUndeployremoves it.- Logs and errors do not expose Customer data or tokens.
After deployment, verify the complete provider flow:
- Confirm that one Subscription exists with the expected key, destination, and Message filters.
- Trigger every supported Message through a representative Customer or Order operation.
- Confirm the Connector receives the event and finishes within the acknowledgment deadline.
- In the provider activity log, confirm the recipient, template, locale, and non-sensitive template data.
- Confirm inbox delivery using an approved live test recipient. Provider acceptance alone is not proof of delivery. A provider sandbox can accept a request while suppressing inbox delivery or restricting recipients.
- Redeliver the same event or replay an equivalent fixture. Confirm the result matches the selected duplicate policy.
- Force a transient provider failure and confirm retry or durable-queue recovery. Force a permanent failure and confirm alerting without an endless retry.
- Exercise rapid state transitions, a return update, a token flow, and locale fallback.
Monitor Subscription health, event age, processing duration, acknowledgment failures, provider response categories, retries, and deduplication conflicts. Also monitor permanent failures and provider delivery outcomes. Define how operators replay failed work without bypassing deduplication. Account for queue delivery delay and cold starts when setting an alert threshold. Subscription delivery has no guaranteed time frame and can be delayed by several minutes.
Troubleshoot observable symptoms
| Symptom | Likely causes | Checks and resolution |
|---|---|---|
| No events reach the Connector | The Subscription is missing, postDeploy failed, delivery stopped after a configuration error, the Message filter is too narrow, or the destination and Region configuration disagree. | Query the Subscription by key and inspect its health. Compare its destination and Message types with the deployed event application and CTP_REGION. Review lifecycle logs. |
| Delivery requests do not reach the handler | The application route differs from the connect.yaml endpoint. | Make the deployed route and endpoint identical. The template uses /mailSender. |
| Events retry or arrive late | The handler misses the 10-second acknowledgment deadline because of a cold start, resource retrieval, or a slow provider call. A default sandbox cold start can take approximately 15 seconds. Subscription delivery can also be delayed by several minutes. | Measure each stage. Reduce startup work, set provider timeouts, and durably enqueue before acknowledging when the design permits it. Account for the selected deployment environment in tests. |
| Logs show success, but no provider send exists | The provider method is still the scaffold logger, or the at-most-once handler acknowledged before a later failure. | Confirm that a provider SDK or API call runs. Check provider activity, not only Connector logs. Implement recovery for work after an early acknowledgment. |
| The provider accepted the request, but no email reached the inbox | The provider is in sandbox mode, the recipient is restricted, the sender domain is unverified, or the provider suppressed or rejected delivery later. | Inspect provider delivery events and sender verification. Test inbox delivery with an approved live configuration. |
| A Customer receives duplicate emails | At-least-once delivery has no durable deduplication, the deduplication is only in memory, the provider result was ambiguous, or duplicate Subscriptions exist. | Inspect the stable key and durable state, reconcile provider activity, reuse a provider idempotency key, and query Subscriptions for lifecycle duplicates. |
| An email disappears after a transient failure | The handler acknowledged before sending, so the Subscription does not redeliver. | Use send-or-persist-before-ack with durable deduplication for drop-intolerant emails, or move retries to a durable queue. |
| A token link is empty | The token validity exceeds 60 minutes and the handler expected the value in the Message. | Use a validity of 60 minutes or less, or create and use a token in the handler. |
| Token emails repeat continuously | A handler-created token triggers the same handler, or redelivery creates another token. | Verify the self-created-client filter and durable deduplication before creating a token. |
| An email uses the wrong language or empty localized values | The handler uses a hardcoded locale or has no fallback for the selected template and localized fields. | Trace the locale source and fallback sequence. Add a template and value fallback test. |
| A state email is stale or arrives for every transition | The handler uses a later re-fetched state without a transition policy, or subscribes broadly without a state allowlist. | Compare the Message transition with the retrieved Order and apply the documented policy and allowlist. |
| A return email incorrectly says a refund completed | Return-information Messages were treated as payment confirmation. | Change the template to describe the return update, or trigger refund wording from an authoritative payment event. |
| Logs expose Customer or token data | Full envelopes, addresses, names, or template payloads are logged. | Replace payload logs with correlation IDs and non-sensitive outcome fields. Redact existing retained logs according to your incident process. |
| Customer or Order API requests slow down or fail when email is unavailable | Email sending was coupled through an API Extension. | Move the sender to an asynchronous event application. |
| Marketing messages or broad Customer data reach the provider | The transactional sender was reused for marketing or the Subscription and mapping include excess data. | Restrict Message types and template fields. Use a separate consent-aware marketing integration for campaigns and audience synchronization. |
| Deployment succeeds but authorization fails at runtime | The API Client scopes do not cover Subscription lifecycle, resource retrieval, or token creation. | Compare the enabled email flows with manage_subscriptions, Customer, Order, and token requirements. Keep the final set least-privileged. |