medusa-plugin-postmark
![stars - medusa-plugin-postmark](https://github.com/Fullstak-nl/medusa-plugin-postmark)
![forks - medusa-plugin-postmark](https://github.com/Fullstak-nl/medusa-plugin-postmark)
![GitHub tag](https://github.com/Fullstak-nl/medusa-plugin-postmark/releases/)
![License](#license)
![issues - medusa-plugin-postmark](https://github.com/Fullstak-nl/medusa-plugin-postmark/issues)
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
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495const 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):
In your JSON templates you can use several types (and variables):
for (local) imagesCopy to clipboardimage
for simple words, (long) sentences, paragraphs and linksCopy to clipboardtext
for moving the cursor down one lineCopy to clipboardmoveDown
for a horizontal lineCopy to clipboardhr
for a table(-like) rowCopy to clipboardtableRow
for looping over items in an orderCopy to clipboarditemLoop
for ending the item loopCopy to clipboarditemLoopEnd
Example:
1234567891011121314151617181920212223242526272829[{"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:Copy to clipboard/src/images/
1234567{"type": "image","image": "image.png","x": 100,"y": 100,"fit": [200, 50]}
has multiple options, see here for more info.Copy to clipboardfit
Optional:
horizontally align the image, the possible values areCopy to clipboardalign
,Copy to clipboardleft
, orCopy to clipboardcenter
Copy to clipboardright
vertically align the image, the possible values areCopy to clipboardvalign
,Copy to clipboardtop
, orCopy to clipboardcenter
Copy to clipboardbottom
text
Text can be used for words, sentences, paragraphs and links.
1234{"type": "text","text": "This is a text"}
If you use
correct you won't need to useCopy to clipboardmoveDown
andCopy to clipboardx
for the text.Copy to clipboardy
Optional:
These options can be used to style the text or to position it.
the x position of the textCopy to clipboardx
the y position of the textCopy to clipboardy
the font of the textCopy to clipboardfont
the font size of the textCopy to clipboardsize
the color of the text (Hex codesCopy to clipboardcolor
)Copy to clipboard#ff0000
the width of the textCopy to clipboardwidth
the alignment of the text, the possible values areCopy to clipboardalign
,Copy to clipboardleft
,Copy to clipboardcenter
, orCopy to clipboardright
.Copy to clipboardjustify
For more styling options, see here for more info.
moveDown
This is used to move the cursor down one or more line(s).
1234{"type": "moveDown","lines": 1}
hr
This is used to draw a horizontal line.
123{"type": "hr"}
Optional:
the color of the line (Hex codesCopy to clipboardcolor
)Copy to clipboard#ff0000
the width of the line if you don't want it to be the full width of the pageCopy to clipboardwidth
the height of the line element, including paddingCopy to clipboardheight
the y position of the line if you can not rely on the cursor (affected byCopy to clipboardy
)Copy to clipboardmoveDown
tableRow
This is used to draw a table row.
12345678910111213{"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.Copy to clipboardtext
itemLoop
This is used to start the loop of items in an order.
To access item variables, use the
To access item variables, use the
object, for exampleCopy to clipboarditem
.Copy to clipboard{{ item.title }}
123{"type": "itemLoop"}
itemLoopEnd
This is used to end the loop of items in an order.
123{"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:
Order item variables are available inside the
If you want to include (simple) if statements, use the following syntax:
Possible variables depend on your notification system. You can use the
Depending on the plugin you use, (almost) every plugin that supports attachments based on
More information on the possible values that
To use a variable, use the following syntax:
, for exampleCopy to clipboard{{ variable_name }}
.Copy to clipboard{{ order.customer.first_name }}
Order item variables are available inside the
andCopy to clipboarditemLoop
elements, for exampleCopy to clipboarditemLoopEnd
.Copy to clipboard{{ item.title }}
If you want to include (simple) if statements, use the following syntax:
, or as a negativeCopy to clipboard{{ if variable_name }}...{{ endif }}
.Copy to clipboard{{ if not variable_name }}...{{ endif }}
Possible variables depend on your notification system. You can use the
object and every template has his ownCopy to clipboardoptions
object.Copy to clipboarddata
Depending on the plugin you use, (almost) every plugin that supports attachments based on
has the same variableCopy to clipboardmedusa-plugin-sendgrid
after theCopy to clipboardorder
variable which holds all the plugin variables.Copy to clipboardoptions
More information on the possible values that
can have can be found here.Copy to clipboardorder
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:Copy to clipboardtoLocaleDateString
.Copy to clipboard{{ order.placed_at | date('en-US',{'year': 'numeric', 'month': 'long', 'day': 'numeric'}) }}
- Currency is formatted using the
function and can be used like this:Copy to clipboardnew Intl.NumberFormat()
.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:
Alter medusa-config.js plugin options:
12345// 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.
123POSTMARK_SERVER_API=""POSTMARK_FROM=""POSTMARK_ORDER_PLACED=1234
The
email address must be a verified sender in your Postmark account.Copy to clipboardPOSTMARK_FROM
Default templates
We've created a few default templates (thanks to pdfkit invoice example) which can be altered to your needs:
Copy to clipboardheader.json
1234567891011121314151617181920212223242526272829303132333435363738[{"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 clipboardcreateInvoice.json
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214[{"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 clipboardfooter.json
12345678910[{"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.