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

medusa-contact-us

v0.0.29

Published

Manage storefront email subscriptions (opt-ins and opt-outs) in Medusa Admin.

Readme

medusa-contact-us

Medusa v2 plugin for contact request workflows and storefront email subscription management with admin tooling.

Plugin Overview

medusa-contact-us provides two core capabilities:

  • Contact requests: create, list, assign, comment, and status-manage customer support/contact entries.
  • Contact email subscriptions: capture and update storefront subscription/unsubscription state.

It includes:

  • Two Medusa modules (contact_requests, contact_email_subscriptions)
  • Store and admin API routes
  • Workflows/steps for request creation and status transitions
  • Optional notification dispatch on configured status transitions
  • Admin route extensions for managing requests and subscriptions
  • Storefront helper utilities for easier frontend integration

This solves the need for a structured contact/support intake workflow and consent list management directly in Medusa.

Medusa Version

  • Built for Medusa v2 (@medusajs/framework / @medusajs/medusa 2.11.2 in package).

Installation & Setup

1) Install

npm install medusa-contact-us
yarn add medusa-contact-us

2) Register plugin and modules in medusa-config.ts

import { defineConfig } from "@medusajs/framework/utils"
import {
  ContactSubscriptionModule,
  ContactRequestModule,
} from "medusa-contact-us"

export default defineConfig({
  modules: [
    ContactSubscriptionModule,
    ContactRequestModule,
  ],
  plugins: [
    {
      resolve: "medusa-contact-us",
      options: {
        default_status: "pending",
        payload_fields: [],
        allowed_statuses: ["pending", "in_progress", "resolved", "closed"],
        status_transitions: [
          { from: null, to: "pending", send_email: false },
          { from: "pending", to: "in_progress", send_email: true },
          { from: "in_progress", to: "resolved", send_email: true },
          { from: "resolved", to: "closed", send_email: false },
        ],
        email: {
          enabled: true,
          default_subject: "Contact Request Status Update",
          default_template: null,
        },
      },
    },
  ],
})

3) Run database migrations

npx medusa db:migrate

Configuration (config.ts / plugin options)

Options are resolved in modules/contact-requests/utils/resolve-options.ts.

| Option | Type | Required | Default | Description | |---|---|---|---|---| | default_status | string | No | "pending" | Initial status for new contact requests. | | payload_fields | PayloadFieldConfig[] | No | [] | Dynamic payload validation schema for custom request fields. | | allowed_statuses | string[] | No | ["pending","in_progress","resolved","closed"] | Allowed status values. | | status_transitions | StatusTransitionConfig[] | No | built-in transition chain | Defines valid status changes and optional email behavior per transition. | | email.enabled | boolean | No | true | Enables notification send attempts in status workflow. | | email.default_subject | string | No | "Contact Request Status Update" | Fallback email subject. | | email.default_template | string \| null | No | null | Fallback template identifier/path if provided. |

PayloadFieldConfig shape

| Field | Type | Required | Notes | |---|---|---|---| | key | string | Yes | Payload field key. | | type | "text" \| "textarea" \| "number" \| "email" \| "select" \| "checkbox" | Yes | Runtime validator uses this type. | | required | boolean | No | Requires field presence in payload. | | label | string | No | UI/display metadata. | | placeholder | string | No | UI/display metadata. | | options | { value: string; label: string }[] | No | Used by select type validation. | | validation | { min?: number; max?: number; pattern?: string } | No | Numeric/string rules used during payload validation. |

StatusTransitionConfig shape

| Field | Type | Required | Notes | |---|---|---|---| | from | string \| null | Yes | null means initial transition. | | to | string | Yes | Next status. | | send_email | boolean | No | If true and email enabled, workflow attempts notification. | | email_template | string | No | Transition-specific template override. | | email_subject | string | No | Transition-specific subject override. |

Environment Variables

The plugin source directly references one env variable:

| Variable | Required | Purpose | Example | |---|---|---|---| | NODE_ENV | No | Adds stack traces to certain API error responses in development mode. | development |

⚠️ Note: Frontend/helper examples may use environment variables in the host app, but plugin runtime logic only directly reads NODE_ENV.

REST APIs / Routes

Store routes

1) POST /store/contact-email-subscriptions

  • Auth: Public (AUTHENTICATE = false)
  • Body:

| Field | Type | Required | |---|---|---| | email | string (email) | Yes | | status | "subscribed" \| "unsubscribed" | No | | metadata | Record<string, unknown> | No | | source | string | No |

  • Response: { subscription }

2) GET /store/contact-requests

  • Auth: Customer required (AUTHENTICATE = true, actor_type must be customer)
  • Query:

| Field | Type | Required | Default | |---|---|---|---| | limit | number | No | 10 | | offset | number | No | 0 | | order | "created_at" \| "updated_at" | No | "created_at" | | order_direction | "ASC" \| "DESC" | No | "DESC" |

  • Behavior: returns only requests matching authenticated customer email; includes comments per request.
  • Response: { requests, count, offset, limit }

3) POST /store/contact-requests

  • Auth: Customer required (AUTHENTICATE = true)
  • Body:

| Field | Type | Required | |---|---|---| | email | string (email) | Yes | | payload | Record<string, unknown> | No | | metadata | Record<string, unknown> | No | | source | string | No |

  • Behavior: runs create-contact-request workflow.
  • Response: { request }

4) GET /store/contact-requests/:id

  • Auth: Customer required (AUTHENTICATE = true)
  • Behavior: ensures request belongs to authenticated customer email.
  • Response: { request, comments }

Admin routes

5) GET /admin/contact-email-subscriptions

  • Auth: Admin route
  • Query:

| Field | Type | Required | Default | |---|---|---|---| | status | "subscribed" \| "unsubscribed" | No | - | | q | string | No | - | | limit | number | No | 50 | | offset | number | No | 0 |

  • Response: { subscriptions, count, offset, limit }

6) GET /admin/contact-requests

  • Auth: Admin route
  • Query:

| Field | Type | Required | |---|---|---| | email | string | No | | status | string | No | | source | string | No | | created_at.gte | datetime string | No | | created_at.lte | datetime string | No | | limit | number (1..100) | No | | offset | number (>=0) | No | | order | "created_at" \| "updated_at" \| "email" | No | | order_direction | "ASC" \| "DESC" | No |

  • Response: { requests, count, offset, limit }

7) POST /admin/contact-requests

  • Auth: Admin route
  • Body: same shape as store create request
  • Behavior: source defaults to "admin"; runs create workflow.
  • Response: { request }

8) GET /admin/contact-requests/:id

  • Auth: Admin route
  • Response: { request, comments, next_allowed_statuses }

9) POST /admin/contact-requests/:id/status

  • Auth: Admin route
  • Body: { status: string }
  • Behavior: runs update status workflow + transition resolution + optional notification.
  • Response: { request }

10) POST /admin/contact-requests/:id/assign

  • Auth: Admin route
  • Body: { assign_to: string | null }
  • Response: { request }

11) GET /admin/contact-requests/:id/comments

  • Auth: Admin route
  • Response: { comments }

12) POST /admin/contact-requests/:id/comments

  • Auth: Admin route
  • Body:

| Field | Type | Required | |---|---|---| | comment | string | No | | images | string[] | No |

  • Response: { comment } (HTTP 201)

Health routes

13) GET /admin/plugin

  • Returns HTTP 200.

14) GET /store/plugin

  • Returns HTTP 200.

Important cURL examples

curl -X POST "http://localhost:9000/store/contact-email-subscriptions" \
  -H "Content-Type: application/json" \
  -H "x-publishable-api-key: pk_test_xxx" \
  -d '{"email":"[email protected]","status":"subscribed","source":"footer"}'
curl -X POST "http://localhost:9000/admin/contact-requests" \
  -H "Authorization: Bearer <ADMIN_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","payload":{"subject":"Help","message":"Need support"}}'
curl -X POST "http://localhost:9000/admin/contact-requests/creq_123/status" \
  -H "Authorization: Bearer <ADMIN_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"status":"in_progress"}'

Services

1) ContactSubscriptionModuleService

Location: src/modules/contact-subscriptions/service.ts

Manages email subscription records.

Key methods:

| Method | Description | |---|---| | upsertSubscription(input) | Creates/updates subscription by normalized email; handles unsubscribed_at. | | listWithFilters(selector, config) | Filtered/paginated list wrapper. | | normalizeSubscription(raw) | Normalizes raw data to DTO shape. |

2) ContactRequestModuleService

Location: src/modules/contact-requests/service.ts

Manages request lifecycle, payload validation, status transitions, assignment, and comments.

Key methods:

| Method | Description | |---|---| | createRequest(input) | Validates email/payload fields, sets default status and status history. | | updateStatus(input) | Enforces transition rules and updates status history. | | listRequests(selector, config) | Filtered/paginated listing for admin/store use. | | getRequest(id) | Retrieves single request DTO. | | assignRequest(input) | Assigns/unassigns request owner/admin reference. | | createComment(input) / listComments(contactRequestId) | Manages request comments with image URLs. | | getOptions() / getNextAllowedStatuses() / getStatusTransition() | Exposes resolved workflow/status config behavior. |

Workflows & Steps (Medusa v2)

Workflows

| Workflow | Input | Output | Purpose | |---|---|---|---| | create-contact-request | CreateContactRequestInput | { request } | Creates new contact request through step abstraction. | | update-contact-request-status | UpdateContactRequestStatusInput | { request } | Updates status, resolves transition, optionally sends notification. |

Steps

| Step | Purpose | |---|---| | create-contact-request | Calls module service createRequest. | | update-contact-request-status | Updates status and captures previous status. | | resolve-status-transition | Loads transition config + resolved plugin options. | | send-status-notification | Sends notification via Modules.NOTIFICATION when enabled/configured. |

Subscribers / Event Hooks

No subscribers/event handlers are implemented.

Admin UI / Widgets

Implemented as admin route extensions:

| Route | Location in Admin | Renders | Interactions / Data | |---|---|---|---| | Contact email list | Sidebar (Envelope icon) | Subscription table with filters/search/load-more | Calls /admin/contact-email-subscriptions; status/search filtering. | | Contact Requests | Sidebar (ChatBubbleLeftRight icon) | Requests table with status/source/email filters | Calls /admin/contact-requests; navigates to detail view. | | Contact Request Details | Detail page route | Full request view: status updates, assignment dropdown (admin users), comment management with image upload | Uses /admin/contact-requests/:id, status/assign/comments endpoints, /admin/users, and /admin/uploads. |

No defineWidgetConfig zone widgets are implemented.

Models & Entities

contact_email_subscription

Fields:

| Field | Type | Nullable | |---|---|---| | id | id | No | | email | text | No | | status | text | No | | metadata | json | Yes | | source | text | Yes | | unsubscribed_at | datetime | Yes |

Migration includes timestamps (created_at, updated_at, deleted_at) and indexes on email (unique), status, deleted_at.

contact_request

Fields:

| Field | Type | Nullable | |---|---|---| | id | id | No | | email | text | No | | payload | json | Yes | | status | text | No | | status_history | json | Yes | | metadata | json | Yes | | source | text | Yes | | assign_to | text | Yes |

Migration adds indexes on email, status, source, created_at, deleted_at, and assign_to.

contact_request_comment

Fields:

| Field | Type | Nullable | |---|---|---| | id | id | No | | admin_id | text | No | | contact_request_id | text | No | | comment | text | Yes | | images | json | Yes |

Migration adds timestamps and indexes on contact_request_id, admin_id, deleted_at.

Relationships to core Medusa entities are not explicitly modeled via ORM relations; linking is via IDs/fields (for example assign_to, admin_id).

Use Cases & Examples

  1. Storefront contact form ingestion

    • Submit via POST /store/contact-requests, then manage in admin queue.
  2. Support team status workflow

    • Move requests through configured transitions (pending -> in_progress -> resolved) using admin status endpoint.
  3. Status update notifications

    • Configure transitions with send_email: true and let workflow trigger notification service dispatch.
  4. Agent assignment and internal notes

    • Assign requests to admins and add comments/images on detail page.
  5. Newsletter consent tracking

    • Use POST /store/contact-email-subscriptions helper-backed flow to maintain subscribe/unsubscribe state.

Troubleshooting

Invalid status transition errors

  • Cause: attempted transition not defined in status_transitions.
  • Fix: update plugin options or use next_allowed_statuses from detail endpoint to drive allowed UI actions.

Payload validation errors on request creation

  • Cause: payload fields/types do not match configured payload_fields.
  • Fix: align payload keys/types and required fields with plugin config.

Notification send failures

  • Cause: Modules.NOTIFICATION not configured or incompatible interface (create/createNotifications missing).
  • Fix: configure Medusa notification module/provider and verify transition/email settings.

Comments endpoint returns empty with warning

  • Cause: comment table migration not applied.
  • Fix: run npx medusa db:migrate to create contact_request_comment.

Unauthorized store request access

  • Cause: store contact request routes require authenticated customer (AUTHENTICATE = true and actor type check).
  • Fix: call with valid customer auth/session.