Postmark
Postmark notification plugin for MedusaJS
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.htmlmargin: {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 randomdelay: process.env.POSTMARK_UPSELL_DELAY || 9, // delay in daysvalid: process.env.POSTMARK_UPSELL_VALID || 30, // valid in dayscollection: 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 hourstemplate: 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 hourstemplate: 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 hourstemplate: 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 templateproduct_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):
- for (local) images
- for simple words, (long) sentences, paragraphs and links
- for moving the cursor down one line
- for a horizontal line
- for a table(-like) row
- for looping over items in an order
- 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 and can be used in the template like this:
{"type": "image","image": "image.png","x": 100,"y": 100,"fit": [200, 50]}
has multiple options, see here for more info.
Optional:
- horizontally align the image, the possible values are , , or
- vertically align the image, the possible values are , , or
text
Text can be used for words, sentences, paragraphs and links.
{"type": "text","text": "This is a text"}
If you use correct you won't need to use and for the text.
Optional:
These options can be used to style the text or to position it.
- the x position of the text
- the y position of the text
- the font of the text
- the font size of the text
- the color of the text (Hex codes )
- the width of the text
- the alignment of the text, the possible values are , , , or .
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:
- the color of the line (Hex codes )
- the width of the line if you don't want it to be the full width of the page
- the height of the line element, including padding
- the y position of the line if you can not rely on the cursor (affected by )
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 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 object, for example .
{"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: , for example .
Order item variables are available inside the and elements, for example .
If you want to include (simple) if statements, use the following syntax: , or as a negative .
Possible variables depend on your notification system.
You can use the object and every template has his own object.
Depending on the plugin you use, (almost) every plugin that supports attachments based on has the same variable after the variable which holds all the plugin variables.
More information on the possible values that 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 function and can be used like this: .
- Currency is formatted using the function and can be used like this: .
- Country can be formatted from ISO to the full country name and can be used like this: .
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 pluginevents: {order: {placed: { nl: 1234, en: 1235 },// rest of the events...
The api key and templates are pulled from env variables.
The 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:
[{"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}]
[{"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}]}]
[{"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.