Add ability to edit campaigns through the API

The edit campaign endpoint in the REST API is very limited: it only allows changing name, alias, description and isPublished. There’s no way to edit the actual campaign flowchart through the API.

Our use case is that we have a campaign that is re-used every few months to send 30+ emails, each scheduled on a particular date. To update the dates manually is tedious and error-prone. (The “after a relative time period” option doesn’t work for us, because late subscribers are not supposed to lag behind; they should receive each email at the same moment as everyone else.)

I appreciate that such an API will be more difficult to implement than simple scalar field editing, but it would be very useful for us.

Right now I have a snippet of JavaScript, which I paste into my browser’s developer console while I have the Campaign Builder open. It talks to the DOM to automate all the clicking and field editing, but of course it’s a horrid hack and might break with every new Mautic update.

2 Likes

Hi @thomastc!
did you get any updates on this issue?
and can you share that Script, which you’re currently using talk to the DOM?
Thanks,

1 Like

I discovered that editing campaigns through the API is supported after all; it’s just not documented. At least, that’s what my notes say… we changed our campaign structure so that the ugly script wasn’t needed anymore.

But here’s the script for reference:

(function($, startDateStr) {
  function delay(millis) {
    return new Promise((resolve, reject) => {
      window.setTimeout(resolve, millis)
    })
  }
  function waitFor(queryFn, testFn, timeoutMillis) {
    if (timeoutMillis === undefined) {
      timeoutMillis = 5000
    }
    const deadline = Date.now() + timeoutMillis
    return new Promise((resolve, reject) => {
      const intervalId = window.setInterval(() => {
        if (Date.now() < deadline) {
          const result = queryFn()
          if (testFn(result)) {
            window.clearInterval(intervalId)
            resolve(result)
          }
        } else {
          window.clearInterval(intervalId)
          reject(`Timed out after ${timeoutMillis} ms waiting for ${queryFn} to pass test ${testFn}`)
        }
      }, 100)
    })
  }
  function presence(queryFn, timeoutMillis) {
    return waitFor(queryFn, (result) => result.length > 0, timeoutMillis)
  }
  function hidden(queryFn, timeoutMillis) {
    return waitFor(queryFn, (result) => result.is(':visible'), timeoutMillis)
  }
  async function run(startDate) {
    const sendEvents = []
    $('.list-campaign-event').each(function(i, box) {
      const $box = $(box)
      const name = $box.find('.campaign-event-name').text()
      const match = /Dag\s+(\d+)(?:\D|$)/.exec(name)
      if (match) {
        const dayOffset = parseInt(match[1]) - 1
        const sendDate = new Date(startDate)
        sendDate.setDate(sendDate.getDate() + dayOffset)
        sendEvents.push({$box, name, sendDate})
      }
    })

    for (const {$box, name, sendDate} of sendEvents) {
      console.log(`Setting date on ${name} to ${sendDate}...`)
      $box.find('.btn-edit').click()
      const $modal = await presence(() => $('#CampaignEventModal'))

      const executeAtDateBtn = await presence(() => $modal.find('[name="campaignevent[triggerMode]"][value="date"]'))
      executeAtDateBtn.click()

      const dateInput = await presence(() => $modal.find('[name="campaignevent[triggerDate]"]'))
      dateInput.val(sendDate.toISOString().substr(0, 16).replace('T', ' '))

      const updateBtn = $modal.find('.btn-save')
      updateBtn.click()

      await hidden(() => $('#CampaignEventModal'))
      
      // Not sure why this is needed.
      await delay(1000)
    }
  }
  
  run(Date.parse(startDateStr)).catch((err) => console.error(err))
})(jQuery, '2020-01-01T05:00:00Z')
1 Like

you’re talking about campaign builder, right?
can you also guide me regarding that as well?
Thanks in Advance.

1 Like

Hey @thomastc, we’ve also just stumbled upon this issue. No matter what we pass under events, the campaign will not get edited / updated, unless it is being created. In that case everything under events is respected.

Have you found a solution? It feels weird to have to delete & re-create a campaign each time you want to update it via the API.

1 Like