Overview
Icon for Postal Notifications

Postal Notifications

Send transactional emails through Postal

@uhlhosting/medusa-notification-postal

A production-ready Postal notification provider for Medusa. Designed for reliable transactional email delivery through Postal's HTTP API, strong configuration validation, template-based workflows, and seamless integration with Medusa’s notification system.

Release

  • Current package version:
  • License: Copy to clipboardMIT
  • Changelog: Copy to clipboardCHANGELOG.md

Options

  • Copy to clipboardauth_type - Postal API mode
  • Copy to clipboardfrom - default sender e-mail address

Postal API settings

  • Copy to clipboardbase_url - Postal base URL, for example Copy to clipboardhttps://postal.example.com
  • Copy to clipboardapi_key - Postal server API key used in Copy to clipboardX-Server-API-Key

Copy to clipboardauth_type only accepts Copy to clipboardsmtp-api (the Postal HTTP API); any other value is rejected at startup.

Environment variables

The provider options above are typically wired from environment variables. The plugin also reads the following at runtime:

Variable Secret Purpose Copy to clipboardPOSTAL_AUTH_TYPE no Auth mode; only Copy to clipboardsmtp-api is supported (default Copy to clipboardsmtp-api). Copy to clipboardPOSTAL_FROM no Default sender address (Copy to clipboardfrom option). Copy to clipboardPOSTAL_BASE_URL no Postal base URL (Copy to clipboardbase_url option). Must be Copy to clipboardhttp/Copy to clipboardhttps. Copy to clipboardPOSTAL_API_KEY yes Postal server API key (Copy to clipboardapi_key option). Copy to clipboardPOSTAL_WEBHOOK_TOKEN yes Shared secret in the tokenized webhook path; generated if unset. Copy to clipboardPOSTAL_REQUEST_TIMEOUT_MS no Outbound Postal HTTP timeout in ms (default Copy to clipboard10000). Copy to clipboardPOSTAL_TEST_TO no Default recipient for admin test sends. Copy to clipboardPOSTAL_TEMPLATE_REGISTRY no JSON overriding the built-in template registry. Copy to clipboardPOSTAL_TEMPLATE_ORDER no Comma-separated template display order. Copy to clipboardPOSTAL_WEBHOOK_TAG_PREFIX no Overrides the tag prefix used to correlate webhook callbacks. Copy to clipboardPOSTAL_WEBHOOK_EVENTS_TABLE no Overrides the webhook events table name. Copy to clipboardPOSTAL_PROVIDER_ID no Overrides the notification provider id. Copy to clipboardPOSTAL_PLUGIN_MODULE no Overrides the plugin module registration name.

Keep the secret variables out of logs and client-visible surfaces; the admin settings endpoint never returns them.

Settings persistence

Non-secret settings edited in the admin (Copy to clipboardfrom, Copy to clipboardbase_url, Copy to clipboardtest_to) persist in the Copy to clipboardpostal_setting table via the plugin module — run Copy to clipboardmedusa db:migrate after installing. Secrets (Copy to clipboardPOSTAL_API_KEY, Copy to clipboardPOSTAL_WEBHOOK_TOKEN) are sourced from the environment/provider options only, are read-only in the admin UI, and are never written to disk. Changes that affect the constructed provider take effect after a backend restart.

Usage

Register the plugin package so its module, migrations, routes, and Admin extension are loaded. Then register the explicit Postal provider subpath under the Notification Module:

module.exports = defineConfig({
plugins: [
{
resolve: "@uhlhosting/medusa-notification-postal",
options: {},
},
],
modules: [
{
resolve: "@medusajs/medusa/notification",
options: {
providers: [
{
resolve:
"@uhlhosting/medusa-notification-postal/providers/postal",
id: "postal",
options: {
channels: ["email"],
auth_type: "smtp-api",
from: process.env.POSTAL_FROM,

Workflow tracking

Use Medusa notification workflows and pass workflow metadata in Copy to clipboardprovider_data:

await notificationModuleService.createNotifications({
channel: "email",
to: "cosmin@example.com",
template: "order-placed",
content: {
subject: "Order confirmation",
html: "<p>Thanks for your order</p>",
text: "Thanks for your order",
},
provider_data: {
workflow_event: "order.placed",
workflow_run_id: "wf_run_123",
},
})

The provider logs Copy to clipboardworkflow_event and Copy to clipboardworkflow_run_id for traceability in Medusa runtime logs.

Postal Webhooks

The plugin now exposes a public ingestion endpoint for Postal delivery lifecycle webhooks:

POST /postal/webhooks/<postal-webhook-token>

The exact tokenized URL is shown in the Postal admin activity page after you save settings. The settings screen intentionally only shows the callback path so the secret stays out of the configuration surface.

It accepts the Postal message status events documented by Postal:

  • Copy to clipboardMessageSent
  • Copy to clipboardMessageDelayed
  • Copy to clipboardMessageDeliveryFailed
  • Copy to clipboardMessageHeld
  • Copy to clipboardMessageBounced
  • Copy to clipboardMessageLinkClicked
  • Copy to clipboardMessageLoaded
  • Copy to clipboardDomainDNSError

Incoming webhook payloads are stored as raw JSON with normalized status metadata, so you can inspect delivery state changes in the admin Postal page after Postal calls back into Medusa.

The admin page also shows a webhook event log and the endpoint to configure inside Postal.

Postal's HTTP payload docs are separate from webhook delivery callbacks and are mainly useful if you are also handling inbound mail by HTTP. Postal's auto-responder, bounce, wildcard, and address-tag docs are relevant when you want to route inbound mail or reason about delivery replies, but they do not change the webhook callback contract itself.

Template registry and metadata passthrough

The plugin includes a built-in template registry for common notification flows:

Template Purpose Typical Medusa event Notes Copy to clipboarddefault Generic fallback preview Any custom template name Used when no registry match exists and content is incomplete. Copy to clipboardpostal-test Provider transport validation Copy to clipboardpostal.example.test Used for operator sends and transport checks. Copy to clipboardpostal-admin-test Admin settings validation Copy to clipboardadmin.postal.test Used by the admin test-send form. Copy to clipboardorder-placed Customer order confirmation Copy to clipboardorder.placed Shared transactional order mail. Copy to clipboardadmin-invite Native Medusa Admin invitation Copy to clipboardinvite.created, Copy to clipboardinvite.resent Use application-rendered content so the environment-specific invite URL is delivered without logging the token. Copy to clipboardpassword-reset Account password reset Copy to clipboardcustomer.password_reset Shared auth email template. Copy to clipboardemail-verification Account email verification Copy to clipboardcustomer.email_verification Shared auth email template. Copy to clipboardwelcome Customer onboarding Copy to clipboardcustomer.welcome Shared onboarding and first-contact template. Copy to clipboardabandoned-cart Cart recovery Copy to clipboardcart.abandoned Shared recovery reminder template. Copy to clipboardrestock-available Back-in-stock alert Copy to clipboardrestock.available Shared inventory alert template.

If a template key is not in the registry, Postal still uses the provided template string and falls back to the passed content. You can also pass extra tracing data through Copy to clipboardprovider_data.metadata and Copy to clipboardprovider_data.custom_args:

provider_data: {
subject: "Order confirmation",
html: "<p>Thanks for your order</p>",
text: "Thanks for your order",
workflow_event: "order.placed",
workflow_run_id: "wf_run_123",
metadata: {
store: "main",
environment: "production",
},
custom_args: {
order_id: "ord_123",
customer_group: "vip",
},
}

Copy to clipboardcustom_args are normalized into safe email headers for transport-level traceability. Copy to clipboardmetadata stays available in Medusa-side notification data and logs.

The admin Postal settings page uses the same registry for test-send template selection, so the built-in examples stay aligned across the backend and admin UI. The selected template also shows a preview of its subject, text, and HTML in the admin test-send panel. That panel also includes a full example payload with recipient, sender identity, workflow metadata, and sample custom args for the selected template. The same panel now lets you load the example values into the test form with one click, edit the message subject/text/HTML/custom args/metadata, or copy the example JSON directly. It also exposes Copy to clipboardcc, Copy to clipboardbcc, and custom Copy to clipboardheaders so test sends match the provider contract more closely.

You can also set sender identity fields when you need branded mail or a separate reply path:

provider_data: {
from: "no-reply@example.com",
from_name: "Postal Admin",
reply_to: "support@example.com",
cc: "billing@example.com",
bcc: ["archive@example.com"],
headers: {
"X-Trace-Id": "trace_123",
},
}

Copy to clipboardfrom_name formats the sender as Copy to clipboardName <email>. Copy to clipboardreply_to is forwarded to Postal and preserved in provider data.

Programmatic Workflows

You can trigger a direct email notification through the Postal provider programmatically using the Copy to clipboardsendPostalEmailWorkflow. This ensures the mail goes through the provider's standard channel and logs full delivery metadata.

import { sendPostalEmailWorkflow } from "@uhlhosting/medusa-notification-postal"
const { result } = await sendPostalEmailWorkflow(req.scope).run({
input: {
to: "cosmin@example.com",
from: "custom-sender@example.com", // Optional, defaults to POSTAL_FROM
template: "custom-template-id", // Optional
provider_data: {
subject: "Test Programmatic Email",
html: "<p>Hello, this is a test email sent programmatically.</p>",
text: "Hello, this is a test email sent programmatically.",
cc: "billing@example.com",
workflow_event: "admin.test_send",
workflow_run_id: "wf_run_manual_123"
}
}
})
// Result returns the delivery info:
// { success: true, delivery: { message_id: "123", ... } }

You may also like

Browse all integrations

Build your own

Develop your own custom integration

Build your own integration with our API to speed up your processes. Make your integration available via npm for it to be shared in our Library with the broader Medusa community.

gift card interface

Ready to build your custom commerce setup?