Built by

Fullstak-nl

Category

Notification

Version

4.6.1

Last updated

Feb 8, 2024, 23:28:21 PM21 days ago

medusa-plugin-postmark

Notifications plugin for Medusa ecommerce server that sends transactional emails via PostMark.

Features

  • Uses the email templating features built into Postmark
  • You can import/use tools like stripo.email
  • The plugin is in active development. If you have any feature requests, please open an issue.
  • Create PDF invoices and credit notes and attach them to the email
  • Send out upsell emails to customers that have recently placed an order with certain collections
  • Send out automated abandoned cart emails to customers that have abandoned their cart (based on last updated date of cart)

Configuration

Enable in your medusa-config.js file similar to other plugins:
More events? (work in progress within the plugin!) See here
const plugins = [
// ... other plugins
{
resolve: `medusa-plugin-postmark`,
options: {
server_api: process.env.POSTMARK_SERVER_API,
from: process.env.POSTMARK_FROM,
bcc: process.env.POSTMARK_BCC || null,
pdf: {
enabled: process.env.POSTMARK_PDF_ENABLED || false,
settings: {
font: process.env.POSTMARK_PDF_FONT || 'Helvetica',
// [{file: 'yourfont.ttf', name: 'yourfont'},{file: 'yourfont-bold.ttf', name: 'yourfontbold'}]
format: process.env.POSTMARK_PDF_FORMAT || 'A4',
// see supported formats here: https://pdfkit.org/docs/paper_sizes.html
margin: {
top: process.env.POSTMARK_PDF_MARGIN_TOP || '50',
right: process.env.POSTMARK_PDF_MARGIN_RIGHT || '50',
bottom: process.env.POSTMARK_PDF_MARGIN_BOTTOM || '50',
left: process.env.POSTMARK_PDF_MARGIN_LEFT || '50'
},
empty: "" // what to show if variable can't be found. Defaults to __UNDEFINED__
},
header: {
enabled: process.env.POSTMARK_PDF_HEADER_ENABLED || false,
content: process.env.POSTMARK_PDF_HEADER_CONTENT || null,
// loads empty header if null, otherwise loads the file from `POSTMARK_PDF_HEADER_CONTENT`
height: process.env.POSTMARK_PDF_HEADER_HEIGHT || '50'
},
footer: {
enabled: process.env.POSTMARK_PDF_FOOTER_ENABLED || false,
content: process.env.POSTMARK_PDF_FOOTER_CONTENT || null,
// loads empty footer if null, otherwise loads the file from `POSTMARK_PDF_FOOTER_CONTENT`
},
templates: {
invoice: process.env.POSTMARK_PDF_INVOICE_TEMPLATE || null,
credit_note: process.env.POSTMARK_PDF_CREDIT_NOTE_TEMPLATE || null,
return_invoice: process.env.POSTMARK_PDF_RETURN_INVOICE_TEMPLATE || null
}
},
events: {
order: {
placed: process.env.POSTMARK_ORDER_PLACED || null,
canceled: process.env.POSTMARK_ORDER_CANCELED || null,
shipment_created: process.env.POSTMARK_ORDER_SHIPMENT_CREATED || null,
},
customer: {
created: process.env.POSTMARK_CUSTOMER_CREATED || null,
password_reset: process.env.POSTMARK_CUSTOMER_PASSWORD_RESET || null,
},
user: {
created: process.env.POSTMARK_USER_CREATED || null,
password_reset: process.env.POSTMARK_USER_PASSWORD_RESET || null,
},
auth: {
password_reset: process.env.POSTMARK_AUTH_PASSWORD_RESET || null,
verify_account: process.env.POSTMARK_AUTH_VERIFY_ACCOUNT || null,
},
activity: {
inactive_user: process.env.POSTMARK_ACTIVITY_INACTIVE_USER || null,
inactive_customer: process.env.POSTMARK_ACTIVITY_INACTIVE_CUSTOMER || null,
}
},
upsell: {
enabled: process.env.POSTMARK_UPSELL_ENABLED || false,
template: process.env.POSTMARK_UPSELL_TEMPLATE || null, // if you supply multiple templates (comma seperated), the plugin will pick one at random
delay: process.env.POSTMARK_UPSELL_DELAY || 9, // delay in days
valid: process.env.POSTMARK_UPSELL_VALID || 30, // valid in days
collection: process.env.POSTMARK_UPSELL_COLLECTION || null,
},
abandoned_cart: {
enabled: process.env.POSTMARK_ABANDONED_CART_ENABLED || false,
first: {
delay: process.env.POSTMARK_ABANDONED_CART_FIRST_DELAY || 1, // delay in hours
template: process.env.POSTMARK_ABANDONED_CART_FIRST_TEMPLATE || null, // if you supply multiple templates (comma seperated), the plugin will pick one at random
},
second: {
delay: process.env.POSTMARK_ABANDONED_CART_SECOND_DELAY || 24, // delay in hours
template: process.env.POSTMARK_ABANDONED_CART_SECOND_TEMPLATE || null, // if you supply multiple templates (comma seperated), the plugin will pick one at random
},
third: {
delay: process.env.POSTMARK_ABANDONED_CART_THIRD_DELAY || 48, // delay in hours
template: process.env.POSTMARK_ABANDONED_CART_THIRD_TEMPLATE || null, // if you supply multiple templates (comma seperated), the plugin will pick one at random
},
},
default_data: {
// ... default data to be passed to the email template
product_url: process.env.POSTMARK_PRODUCT_URL || '',
product_name: process.env.POSTMARK_PRODUCT_NAME || '',
company_name: process.env.POSTMARK_COMPANY_NAME || '',
company_address: process.env.POSTMARK_COMPANY_ADDRESS || '',
}
}
}
]

Templates

The plugin uses the Postmark template system for emails. For attachments the plugin relies on the pdfkit library.
In your JSON templates you can use several types (and variables):
  • Copy to clipboard
    image
    for (local) images
  • Copy to clipboard
    text
    for simple words, (long) sentences, paragraphs and links
  • Copy to clipboard
    moveDown
    for moving the cursor down one line
  • Copy to clipboard
    hr
    for a horizontal line
  • Copy to clipboard
    tableRow
    for a table(-like) row
  • Copy to clipboard
    itemLoop
    for looping over items in an order
  • Copy to clipboard
    itemLoopEnd
    for ending the item loop
Example:
[
{
"type": "image",
"image": "image.png",
"x": 100,
"y": 100,
"fit": [200, 50]
},
{
"type": "text",
"text": "This is a text",
"size": 20
},
{
"type": "moveDown",
"lines": 2
},
{
"type": "hr"
},
{
"type": "moveDown",
"lines": 2
},
{
"type": "text",
"text": "Another text"
}
]

image

Images are stored in
Copy to clipboard
/src/images/
and can be used in the template like this:
{
"type": "image",
"image": "image.png",
"x": 100,
"y": 100,
"fit": [200, 50]
}
Copy to clipboard
fit
has multiple options, see here for more info.
Optional:
  • Copy to clipboard
    align
    horizontally align the image, the possible values are
    Copy to clipboard
    left
    ,
    Copy to clipboard
    center
    , or
    Copy to clipboard
    right
  • Copy to clipboard
    valign
    vertically align the image, the possible values are
    Copy to clipboard
    top
    ,
    Copy to clipboard
    center
    , or
    Copy to clipboard
    bottom

text

Text can be used for words, sentences, paragraphs and links.
{
"type": "text",
"text": "This is a text"
}
If you use
Copy to clipboard
moveDown
correct you won't need to use
Copy to clipboard
x
and
Copy to clipboard
y
for the text.
Optional:
These options can be used to style the text or to position it.
  • Copy to clipboard
    x
    the x position of the text
  • Copy to clipboard
    y
    the y position of the text
  • Copy to clipboard
    font
    the font of the text
  • Copy to clipboard
    size
    the font size of the text
  • Copy to clipboard
    color
    the color of the text (Hex codes
    Copy to clipboard
    #ff0000
    )
  • Copy to clipboard
    width
    the width of the text
  • Copy to clipboard
    align
    the alignment of the text, the possible values are
    Copy to clipboard
    left
    ,
    Copy to clipboard
    center
    ,
    Copy to clipboard
    right
    , or
    Copy to clipboard
    justify
    .
For more styling options, see here for more info.

moveDown

This is used to move the cursor down one or more line(s).
{
"type": "moveDown",
"lines": 1
}

hr

This is used to draw a horizontal line.
{
"type": "hr"
}
Optional:
  • Copy to clipboard
    color
    the color of the line (Hex codes
    Copy to clipboard
    #ff0000
    )
  • Copy to clipboard
    width
    the width of the line if you don't want it to be the full width of the page
  • Copy to clipboard
    height
    the height of the line element, including padding
  • Copy to clipboard
    y
    the y position of the line if you can not rely on the cursor (affected by
    Copy to clipboard
    moveDown
    )

tableRow

This is used to draw a table row.
{
"type": "tableRow",
"columns": [
{
"text": "Column 1",
"width": 200
},
{
"text": "Column 2",
"width": 150
}
]
}
Optional:
You can use the same options as for
Copy to clipboard
text
to style the text in the table row. If you want a special column styled, you can add the options to the column object.

itemLoop

This is used to start the loop of items in an order.
To access item variables, use the
Copy to clipboard
item
object, for example
Copy to clipboard
{{ item.title }}
.
{
"type": "itemLoop"
}

itemLoopEnd

This is used to end the loop of items in an order.
{
"type": "itemLoopEnd"
}

Variables

In the template you can use variables. These are replaced by the plugin with the correct value.
To use a variable, use the following syntax:
Copy to clipboard
{{ variable_name }}
, for example
Copy to clipboard
{{ order.customer.first_name }}
.
Order item variables are available inside the
Copy to clipboard
itemLoop
and
Copy to clipboard
itemLoopEnd
elements, for example
Copy to clipboard
{{ item.title }}
.
If you want to include (simple) if statements, use the following syntax:
Copy to clipboard
{{ if variable_name }}...{{ endif }}
, or as a negative
Copy to clipboard
{{ if not variable_name }}...{{ endif }}
.
Possible variables depend on your notification system. You can use the
Copy to clipboard
options
object and every template has his own
Copy to clipboard
data
object.
Depending on the plugin you use, (almost) every plugin that supports attachments based on
Copy to clipboard
medusa-plugin-sendgrid
has the same variable
Copy to clipboard
order
after the
Copy to clipboard
options
variable which holds all the plugin variables.
More information on the possible values that
Copy to clipboard
order
can have can be found here.

Variable functions

At the moment the only variable you can use functions with is dates and currency.
  • Dates are formatted using the
    Copy to clipboard
    toLocaleDateString
    function and can be used like this:
    Copy to clipboard
    {{ order.placed_at | date('en-US',{'year': 'numeric', 'month': 'long', 'day': 'numeric'}) }}
    .
  • Currency is formatted using the
    Copy to clipboard
    new Intl.NumberFormat()
    function and can be used like this:
    Copy to clipboard
    {{ order.total_price | currency('en-US') }}
    .
  • Country can be formatted from ISO to the full country name and can be used like this:
    Copy to clipboard
    {{ order.shipping_address.country_code | country }}
    .
    Please make sure that the options are wrapped in single quotes.

Localisation

Want separate templates for different languages?
Alter medusa-config.js plugin options:
// medusa config including the postmark plugin
events: {
order: {
placed: { nl: 1234, en: 1235 },
// rest of the events...
The api key and templates are pulled from env variables.
POSTMARK_SERVER_API=""
POSTMARK_FROM=""
POSTMARK_ORDER_PLACED=1234
The
Copy to clipboard
POSTMARK_FROM
email address must be a verified sender in your Postmark account.

Default templates

We've created a few default templates (thanks to pdfkit invoice example) which can be altered to your needs:
Copy to clipboard
header.json
[
{
"type": "text",
"text": "ACME Inc.",
"size": 20,
"color": "#444444"
},
{
"type": "text",
"text": "ACME Inc.",
"size": 10,
"color": "#444444",
"align": "right",
"x": 200
},
{
"type": "moveDown"
},
{
"type": "text",
"text": "123 Main Street",
"size": 10,
"color": "#444444",
"align": "right",
"x": 200
},
{
"type": "moveDown"
},
{
"type": "text",
"text": "New York, NY, 10025",
"size": 10,
"color": "#444444",
"align": "right",
"x": 200
}
]
Copy to clipboard
createInvoice.json
[
{
"type": "text",
"text": "Invoice",
"size": 20,
"color": "#444444"
},
{
"type": "moveDown"
},
{
"type": "hr"
},
{
"type": "text",
"text": "Invoice Number:",
"size": 10
},
{
"type": "text",
"font": "Helvetica-Bold",
"text": "#{{ order.display_id }}",
"size": 10,
"x": 100
},
{
"type": "text",
"font": "Helvetica-Bold",
"text": "{{ order.shipping_address.first_name }} {{ order.shipping_address.last_name }}",
"size": 10,
"x": 300
},
{
"type": "moveDown"
},
{
"type": "text",
"font": "Helvetica",
"text": "Invoice Date:",
"size": 10
},
{
"type": "text",
"text": "{{ order.created_at | date('en-US',{'year': 'numeric', 'month': 'long', 'day': 'numeric'}) }}",
"size": 10,
"x": 100
},
{
"type": "text",
"text": "{{ order.shipping_address.address_1 }} {{ order.shipping_address.address_2 }}",
"size": 10,
"x": 300
},
{
"type": "moveDown"
},
{
"type": "text",
"text": "{{ order.shipping_address.postal_code }}, {{ order.shipping_address.city }}, {{ order.shipping_address.country_code }}",
"size": 10,
"x": 300
},
{
"type": "moveDown"
},
{
"type": "hr"
},
{
"type": "moveDown",
"lines": 1
},
{
"type": "tableRow",
"font": "Helvetica-Bold",
"columns": [
{
"text": "Item",
"width": 200
},
{
"text": "Quantity",
"width": 50
},
{
"text": "Price",
"width": 50
},
{
"text": "Total",
"width": 50
}
]
},
{
"type": "hr"
},
{
"type": "itemLoop"
},
{
"type": "tableRow",
"font": "Helvetica",
"columns": [
{
"text": "{{ item.title }}",
"width": 200
},
{
"text": "{{ item.quantity }}",
"width": 50
},
{
"text": "{{ item.unit_price | currency('en-US') }}",
"width": 50
},
{
"text": "{{ item.totals.total | currency('en-US') }}",
"width": 50
}
]
},
{
"type": "hr"
},
{
"type": "itemLoopEnd"
},
{
"type": "tableRow",
"columns": [
{
"text": "",
"width": 200
},
{
"text": "",
"width": 50
},
{
"text": "Subtotal",
"width": 50
},
{
"text": "{{ order.subtotal | currency('en-US') }}",
"width": 50
}
]
},
{
"type": "tableRow",
"columns": [
{
"text": "",
"width": 200
},
{
"text": "",
"width": 50
},
{
"text": "Shipping",
"width": 50
},
{
"text": "{{ order.shipping_total | currency('en-US') }}",
"width": 50
}
]
},
{
"type": "tableRow",
"columns": [
{
"text": "",
"width": 200
},
{
"text": "",
"width": 50
},
{
"text": "TAX",
"width": 50
},
{
"text": "{{ order.tax_total | currency('en-US') }}",
"width": 50
}
]
},
{
"type": "tableRow",
"font": "Helvetica-Bold",
"columns": [
{
"text": "",
"width": 200
},
{
"text": "",
"width": 50
},
{
"text": "Total",
"width": 50
},
{
"text": "{{ order.total | currency('en-US') }}",
"width": 50
}
]
}
]
Copy to clipboard
footer.json
[
{
"type": "text",
"text": "Thank you for your business!",
"size": 10,
"color": "#444444",
"width": "full",
"align": "center"
}
]

Acknowledgement

This plugin is originally based on medusa-plugin-sendgrid by Oliver Juhl.

Build your own plugins

Develop your own plugins with our API to speed up your processes.

Make your plugin available via npm for it to be shared in our Plugin Library with the broader Medusa community.