Talking to the outside world
Most real automations end by telling something else: another system, a person, a spreadsheet. This chapter covers the pieces for that: JSON, dates, web requests and mail.
JSON is the text format most web services speak. mash.json.decode turns JSON text into dictionaries and arrays you already know how to use. Pass true as the second argument to get dictionaries:
set text to '{"order": {"ref": "SO-1", "lines": [{"sku": "A", "qty": 2}, {"sku": "B", "qty": 5}]}}'set data to mash.json.decode(text, true)
printline data["order"]["ref"]
set qty to 0each data["order"]["lines"] as line increment qty by line["qty"]endprintline "Total quantity: {{qty}}"SO-1Total quantity: 7text = '{"order": {"ref": "SO-1", "lines": [{"sku": "A", "qty": 2}, {"sku": "B", "qty": 5}]}}'data = mash.json.decode(text, true)
printline(data["order"]["ref"])
qty = 0each (data["order"]["lines"]) as line { qty += line["qty"]}printline("Total quantity: {{qty}}")SO-1Total quantity: 7mash.json.encode goes the other way:
printline mash.json.encode({ref: "SO-2", paid: true, lines: [1, 2]}){"ref":"SO-2","paid":true,"lines":[1,2]}printline(mash.json.encode({ref: "SO-2", paid: true, lines: [1, 2]})){"ref":"SO-2","paid":true,"lines":[1,2]}Text that isn’t valid JSON is an error: decode records json decode - check your input and gives you nothing. Put an error block after the line when the text comes from somewhere you don’t control:
set data to nullset data to mash.json.decode("this is not json")error printline "Bad JSON: {{mash.error.message}}" set data to {}endprintline count(data)Bad JSON: json decode - check your input0data = nulldata = mash.json.decode("this is not json")error { printline("Bad JSON: {{mash.error.message}}") data = {}}printline(count(data))Bad JSON: json decode - check your input0mash.date.now(), mash.date.today() and mash.date.parse("2026-11-03") give you a date. format writes it out using the letters PHP’s date formats use (Y year, m month, d day, H:i time, D/l day name):
set d to mash.date.parse("2026-11-03 14:05:00")printline d.format("D j M Y")printline d.format("H:i")printline d.isWeekend()Tue 3 Nov 202614:05falsed = mash.date.parse("2026-11-03 14:05:00")printline(d.format("D j M Y"))printline(d.format("H:i"))printline(d.isWeekend())Tue 3 Nov 202614:05falseAdding to a date changes it. After
d.addDays(30),ditself is 30 days later. To keep the original, add to a copy withcopy().
set start to mash.date.parse("2026-11-03")set due to start.copy().addDays(30)printline "Start: {{start.format('Y-m-d')}}"printline "Due: {{due.format('Y-m-d')}}"printline "Days between: {{start.diffInDays(due)}}"Start: 2026-11-03Due: 2026-12-03Days between: 30start = mash.date.parse("2026-11-03")due = start.copy().addDays(30)printline("Start: {{start.format('Y-m-d')}}")printline("Due: {{due.format('Y-m-d')}}")printline("Days between: {{start.diffInDays(due)}}")Start: 2026-11-03Due: 2026-12-03Days between: 30diffInDays is signed: it’s negative when the date you pass in is earlier than the one you call it on. Pass true as a second argument for the plain distance. Also available: subDays, addMonths, startOfMonth, endOfMonth. Compare dates with < and >. A file’s modified is a date, so file.modified.format("Y-m-d") works.
The script’s clock is the server’s, not your own. If the time of day matters, say which zone you mean in your output (for example format("Y-m-d H:i P") shows the offset).
Calling a web service
Section titled “Calling a web service”mash.http makes web requests. get, post, put, patch and delete each take a URL and a dictionary of options:
set response to mash.http.get("https://api.example.com/orders/1182", { bearer: "my-token", timeout: 30})
if response.status is 200 set order to response.json printline "Order {{order['ref']}} is {{order['status']}}"else printline "The service said {{response.status}}"endset response to mash.http.get("https://api.example.com/orders/1182", { bearer: "my-token", timeout: 30})
if (response.status == 200) { order = response.json printline("Order {{order['ref']}} is {{order['status']}}")} else { printline("The service said {{response.status}}")}A response has status, body (the text), json (the body decoded) and headers. Useful options:
| Option | What it does |
|---|---|
bearer | sends an Authorization: Bearer ... header |
headers | a dictionary of extra headers |
query | a dictionary added to the URL as ?a=1&b=2 |
json | a dictionary sent as the JSON body |
form_params | a dictionary sent as a web form |
timeout | seconds to wait before giving up |
http_errors | set false to get a 404 or 500 back as a response, not an error |
Sending data:
set response to mash.http.post("https://api.example.com/orders", { bearer: "my-token", json: {ref: "SO-20417", status: "Ordered"}, http_errors: false})printline "Created? {{response.status}}"set response to mash.http.post("https://api.example.com/orders", { bearer: "my-token", json: {ref: "SO-20417", status: "Ordered"}, http_errors: false})printline("Created? {{response.status}}")Never write a password or token into a script that other people can open. Put it in a config parameter, or read it from a service account if your administrator has set one up.
If the call fails (the server is down, the name doesn’t exist), put an error block after it, as in Catching an error in When things go wrong:
set response to mash.http.get("https://invalid.example")error printline "Could not reach the service: {{mash.error.message}}" exitendresponse = mash.http.get("https://invalid.example")error { printline("Could not reach the service: {{mash.error.message}}") exit}Sending email
Section titled “Sending email”mash.comms.mail.send takes a recipient, a subject and a body:
mash.comms.mail.send( "accounts@example.com", "Order SO-20417 filed", "The paperwork for SO-20417 is now in the customer folder.")You can add attachments (an array of files), cc and bcc lists, and a delay in seconds as further arguments:
set report to mash.files.get("Finance", "Reports/week-45.pdf")
mash.comms.mail.send( "accounts@example.com", "Weekly report", "The report is attached.", [report], ["manager@example.com"])report = mash.files.get("Finance", "Reports/week-45.pdf")
mash.comms.mail.send( "accounts@example.com", "Weekly report", "The report is attached.", [report], ["manager@example.com"])Messages go into a queue and leave shortly after, so a script that sends a hundred emails finishes quickly. Test with your own address first.
Other pieces
Section titled “Other pieces”The same style works across the rest of mash: CSV and spreadsheet files (mash.csv, mash.xlsx), PDFs (mash.pdf), images, barcodes, SMS and more. The library reference lists all of them.
Try it
Section titled “Try it”Decode this text and print the customer’s name: {"customer": {"name": "Acme Ltd"}}. Then build a dictionary with your own name and encode it back to JSON.
Next, you’ll put the pieces together into one useful script.