Giter VIP home page Giter VIP logo

medusa-plugin-abandoned-cart's Introduction

Medusa Abandoned Cart Plugin

Still in beta proceed with caution

v2.0.0 - Contains breaking changes. Please read the documentation carefully

You can now send emails with other providers and schedule the task to send emails. Remember to run migrations after adding the plugin to the medusa-config.js file

This plugin adds abandoned cart functionality to Medusa. It allows you to send emails to customers who have abandoned their carts. The plugin uses SendGrid to send the emails. The plugin is written in Typescript.

I strongly recommend using this plugin in combination with the Medusa Plugin SendGrid Typescript to get type safety and autocompletion for the SendGrid API. You can also use the Medusa Plugin SendGrid

Image

Abandoned Cart

Medusa Website | Medusa Repository

Features

  • Send emails to customers who have abandoned their carts manualy.
  • Get a list of abandoned carts in Admin.
  • Send emails with other provider (new)
  • Send emails with scheduled task (new) cron job runs every 5 minutes.

Prerequisites


How to Install

1. Run the following command in the directory of the Medusa backend

yarn add medusa-plugin-abandoned-cart
npm install medusa-plugin-abandoned-cart

2. Set the following environment variable in .env

SENDGRID_API_KEY=<API_KEY>
SENDGRID_FROM=<SEND_FROM_EMAIL>
# IDs for different email templates
SENDGRID_ABANDONED_CART_TEMPLATE=<ORDER_PLACED_TEMPLATE_ID> # example

3. In medusa-config.js add the following at the end of the plugins array

const plugins = [
// ...,
{
  resolve: `medusa-plugin-abandoned-cart`,
  /** @type {import('medusa-plugin-abandoned-cart').PluginOptions} */
  options: {
    sendgridEnabled: true,
    from: process.env.SENDGRID_FROM,
    enableUI: true,
    subject: "You have something in your cart",
    templateId: process.env.SENDGRID_ABANDONED_CART_TEMPLATE,
    days_to_track: 7,
    set_as_completed_if_overdue: true,
    max_overdue: "2h",
    localization: {
      fr: {
        subject: "Vous avez quelque chose dans votre panier",
        templateId: process.env.SENDGRID_ABANDONED_CART_TEMPLATE_FR,
      },
      pl: {
        subject: "Masz coś w koszyku",
        templateId: process.env.SENDGRID_ABANDONED_CART_TEMPLATE_PL,
      },
      en: {
        subject: "You have something in your cart",
        templateId: process.env.SENDGRID_ABANDONED_CART_TEMPLATE,
      },
    },
    intervals: [
      {
        interval: "1h",
        subject: "You have something in your cart",
        templateId: process.env.SENDGRID_ABANDONED_CART_TEMPLATE,
        localization: {
          fr: {
            subject: "Vous avez quelque chose dans votre panier",
            templateId: process.env.SENDGRID_ABANDONED_CART_TEMPLATE_FR,
          },
          pl: {
            subject: "Masz coś w koszyku",
            templateId: process.env.SENDGRID_ABANDONED_CART_TEMPLATE_PL,
          },
          en: {
            subject: "You have something in your cart",
            templateId: process.env.SENDGRID_ABANDONED_CART_TEMPLATE,
          },
        },
      },
      {
        interval: "1d",
        subject: "You have something in your cart",
        templateId: process.env.SENDGRID_ABANDONED_CART_TEMPLATE,
        localization: {
          fr: {
            subject: "Vous avez quelque chose dans votre panier",
            templateId: process.env.SENDGRID_ABANDONED_CART_TEMPLATE_FR,
          },
          pl: {
            subject: "Masz coś w koszyku",
            templateId: process.env.SENDGRID_ABANDONED_CART_TEMPLATE_PL,
          },
          en: {
            subject: "You have something in your cart",
            templateId: process.env.SENDGRID_ABANDONED_CART_TEMPLATE,
          },
        },
      },
      {
        interval: "5d",
        subject: "You have something in your cart",
        templateId: process.env.SENDGRID_ABANDONED_CART_TEMPLATE,
        localization: {
          fr: {
            subject: "Vous avez quelque chose dans votre panier",
            templateId: process.env.SENDGRID_ABANDONED_CART_TEMPLATE_FR,
          },
          pl: {
            subject: "Masz coś w koszyku",
            templateId: process.env.SENDGRID_ABANDONED_CART_TEMPLATE_PL,
          },
          en: {
            subject: "You have something in your cart",
            templateId: process.env.SENDGRID_ABANDONED_CART_TEMPLATE,
          },
        },
      },
    ],
  },
},
];
export interface BasePluginOptions {
/* enable sendgrid */
sendgridEnabled: boolean
/* email from which you will be sending */
from: { name?: string; email: string } | string
/* template id from sendgrid */
templateId: string
/* header line of the email optional */
header?: string
/* number of days to track */
days_to_track?: number
/* subject of the email optional */
subject?: string
localization?: {
  [key: string]: {
    subject?: string
    header?: string
    templateId: string
  };
}
}

export interface IntervalOptions {
/* interval example string "1d", "1h", "30m" 
check parse-duration package for more examples */
interval: string | number
/* subject of the email optional */
subject?: string
/* template id from sendgrid */
templateId?: string
localization?: {
  [key: string]: {
    subject?: string
    header?: string
    templateId: string
  };
}

}

export interface AutomatedAbandonedCart extends BasePluginOptions {
/* intervals */
intervals: Array<IntervalOptions>,
/* max overdue @default "2h"*/
max_overdue: string
/* set as completed if overdue */
set_as_completed_if_overdue: boolean
}

export interface ManualAbandonedCart extends BasePluginOptions {
localization: {
  [key: string]: {
    subject?: string
    header?: string
    templateId: string
  };
}
}

Remember to run migrations after adding the plugin to the medusa-config.js file

4. The Sendgrid Template receives the following

export type NewLineItem = Omit<LineItem, "beforeUpdate" | "afterUpdateOrLoad"> & {
 // human readable price with currency code
 price: string
}

// The cart object is transformed to this format before sending the email
export interface TransformedCart {
 id: string;
 email: string;
 items: NewLineItem[];
 cart_context: Record<string, unknown>;
 first_name: string;
 last_name: string;
 totalPrice: number;
 created_at: Date;
 currency: string;
 region: string;
 country_code: string;
 region_name: string;
 abandoned_count?: number;
 abandoned_lastdate?: Date;
 abandoned_last_interval?: number;
 abandoned_completed_at?: Date;
}

5. When an abandoned cart email is sent, the plugin emits the cart.send-abandoned-email event

You can listen to this event in your plugin to perform additional actions with your custom notification provider or perform extra actions when using Sendgrid.

  • The Event is sent one time when the button is pressed in the Admin UI.

  • The Event gets the following payload:

{
  id: string; // cart id
}

Check medusa docs https://docs.medusajs.com/development/events/create-subscriber

Example

import { 
  ProductService,
  type SubscriberConfig, 
  type SubscriberArgs, 
} from "@medusajs/medusa"
import { EntityManager } from "typeorm";
import { MedusaError } from 'medusa-core-utils';
import { TransformedCart } from "medusa-abandoned-cart-plugin";
import SendGridService from "medusa-plugin-sendgrid-typescript/dist/services/sendgrid";

export default async function abandonedEmailHandler({ 
  data, eventName, container, pluginOptions, 
}: SubscriberArgs<Record<string, any>>) {
  const manager: EntityManager = container.resolve("manager")
  const cartRepo = manager.getRepository(Cart)
  const abandonedCartService: AbandonedCartService = container.resolve("abandonedCartService")
  const sendGridService: SendGridService = container.resolve("sendGridService")

  const { id, interval } = data

  const notNullCartsPromise = await cartRepo.findOne({
    where: {
      id,
    },
    order: {
      created_at: "DESC",
    },
    select: ["id", "email", "created_at", "region", "context", "abandoned_count"],
    relations: ["items", "region", "shipping_address"],
  })

  if (!notNullCartsPromise) {
    throw new MedusaError("Not Found", "Cart not found")
  }
  // do something with the cart...

  // Transform the cart to the format needed for the email template this is mandatory depending on email template and provider
  const cart = abandonedCartService.transformCart(notNullCartsPromise) as TransformedCart

  const emailData = {
    to: cart.email,
    from: this.options_.from,
    templateId: "d-1234567890abcdef",
    dynamic_template_data: {
      ...cart,
    },
  }

  // Send email using sendgrid
  const sendGridPromise = this.sendGridService.sendEmail(emailData)

  // Update the cart to reflect that the email has been sent
  const repoPromise = cartRepo.update(cart.id, {
        abandoned_lastdate: new Date().toISOString(),
        abandoned_count: (notNullCartsPromise?.abandoned_count || 0) + 1,
        abandoned_last_interval: interval || undefined,
        abandoned_completed_at: interval && pluginOptions.intervals[pluginOptions.intervals.length - 1].interval === interval ? new Date().toISOString() : undefined,
      });

  await Promise.all([sendGridPromise, repoPromise])
  
}

export const config: SubscriberConfig = {
  event: "cart.send-abandoned-email",
  context: {
    subscriberId: "abandoned-email-handler",
  },
}

medusa-plugin-abandoned-cart's People

Contributors

luluhoc avatar renovate[bot] avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

medusa-plugin-abandoned-cart's Issues

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Detected dependencies

github-actions
.github/workflows/npm-publish.yml
  • actions/checkout v4
  • actions/setup-node v3
npm
package.json
  • parse-duration ^1.1.0
  • typeorm ^0.3.20
  • @babel/cli ^7.24.5
  • @babel/core ^7.24.5
  • @babel/preset-typescript ^7.24.1
  • @eslint/js ^9.2.0
  • @medusajs/admin ^7.1.14
  • @medusajs/cache-inmemory ^1.8.10
  • @medusajs/event-bus-local ^1.9.8
  • @medusajs/medusa ^1.20.6
  • @medusajs/medusa-cli ^1.3.22
  • @types/express ^4.17.21
  • @types/jest ^29.5.12
  • @types/node ^20.12.11
  • babel-preset-medusa-package ^1.1.19
  • cross-env ^7.0.3
  • eslint ^9.2.0
  • eslint-config-prettier ^9.1.0
  • eslint-plugin-prettier ^5.1.3
  • globals ^15.2.0
  • jest ^29.7.0
  • medusa-interfaces ^1.3.9
  • medusa-plugin-sendgrid-typescript ^2.1.2
  • prettier ^3.2.5
  • rimraf ^5.0.7
  • ts-jest ^29.1.2
  • ts-loader ^9.5.1
  • typescript ^5.4.5
  • typescript-eslint ^7.8.0
  • @medusajs/medusa ^1.9.x
  • medusa-interfaces ^1.3.x
  • typeorm ^0.3.16
  • node >=16
nvm
.nvmrc
  • node v21.7.3

  • Check this box to trigger a request for Renovate to run again on this repository

Enabled use it with any mailing provider

Hello there,

This plugin is heavily tied to the Sendgrid Mail service, but not all people either use it or can pay for it.

Would it be hard to adapt this plugin to use the preferred mailing provider plugin/service?

invalid currency code in NumberFormat(): null

i think somehow soft deleted regions throw null currency resulting in breaking all the dashboard
Screenshot from 2024-03-10 04-34-58
i have alter that by checking the cart.currency first (only done that in the complied code didn't get lucky running the plugin locally without errors ) :

                /* @__PURE__ */ jsx(Table.Cell, { children: cart.first_name + " " + cart.last_name }),
                /* @__PURE__ */ jsx(Table.Cell, { children: cart.email }),
                /* @__PURE__ */ jsx(Table.Cell, { children: cart.items.length }),
                /* @__PURE__ */ jsx(Table.Cell, { children: cart.region_name }),
                /* @__PURE__ */ jsx(Table.Cell, { children: new Date(cart.created_at).toLocaleDateString() }),
                /* @__PURE__ */ jsx(Table.Cell, { className: "text-right", children: cart.currency ? (new Intl.NumberFormat("en-US", {  style: "currency",currency: cart.currency
                }).format(cart.totalPrice / 100)):"NAN" }),

Feature Request: Add automated emails with a configurable schedule

This is a great plugin. It would be nice if we could set a schedule to trigger the emails automatically and space out the time they are sent.

e.g. In the Medusa Config we can set: [1 hr, 1 day, 5 days]. Then a cron job looks for abandoned carts and sends the email 1 hour after the cart has been abandoned. Then it will send another email 1 day after the cart was abandoned. Then finally it will send the email 5 days after it has been abandoned. After this no more emails will be sent.

Abandoned Carts Name shows undefined

For some reason for the medusa admin panel am getting undefined for first name and last name even if the user has entered his/her name during registration

Screenshot 2024-05-17 at 2 09 42 PM

I have installed the medusa-plugin-abandoned-cart and followed the instructions everything seems to work fine but the first name and last name seems to show undefined

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.