5

I'm using Sendgrid's new API (v3) with Node js + libraries @sendgrid/mail and @sendgrid/client.

What I want to achieve: send a weekly digest to all my contacts (except those who unsubscribe from this group). I use a template I created via Sendgrid thanks to its template_id, as well as dynamic template data to populate the mail with the weekly data.

Problem:

  • I can send a marketing campaign using /v3/marketing/singlesends to all my contacts with my template but I can't send it with my dynamic template data.

  • I can send one email at a time with my template + dynamic template data but for this I need to retrieve all my contacts first and I can only retrieve the last 50 contacts from this endpoint /v3/marketing/contacts (they disabled the pagination). I may have been able to retrieve them all from this endpoint /contactdb/recipients, the problem is I created my Sendgrid account after they released their new API so I can't access it.

Any idea on how to perform this?

Current code:

1) Configure campaign

const sgClient = require('@sendgrid/client')
var params={
    'name': 'Weekly Digest #'+nb,
    'sender_id': sg_sender_id,
    'suppression_group_id': sg_unsub_group_id,
    'template_id': id_template,
    'dynamicTemplateData': template_params,
    //also tried with 'dynamic_template_data' instead, not working
    'filter': {'send_to_all': true}
}

let url='/v3/marketing/singlesends/' + campaign_id.toString('base64')
const request = {
    method: 'PATCH',
    url: url,
    body: params
}
sgClient.setApiKey(config.sendgrid_key)
sgClient.request(request)
.then(([response, body]) => {
    console.log('body:', body)
})
.catch(error => {
    console.error('error:', error.toString())
})

2) Send campaign

let url='/v3/marketing/singlesends/' + campaign_id.toString('base64') + '/schedule'
let params={'send_at': 'now'}
const request = {
    method: 'PUT',
    url: url,
    body: params
}
sgClient.setApiKey(config.sendgrid)
sgClient.request(request)
.then(([response, body]) => {
    console.log('body:', body)
})
.catch(error => {
    console.error('error:', error.toString())
})
Marie Dm
  • 2,637
  • 3
  • 24
  • 43
  • Did you figure out a solution to this problem? I am facing the same limitations of their API like you... – makle Sep 24 '20 at 20:34
  • Unfortunately no. I grab my contacts from MailChimp and send a dynamic template with Sendgrid to each one of them. – Marie Dm Sep 25 '20 at 14:24
  • I actually also switched to Mailchimp and found a solution there. Their API seems also better. If you want I can share my code with you? I basically create a campaign and then update the content with a custom html + inject data dynamically. In my case a table. – makle Sep 25 '20 at 21:02
  • Yes, sure! For now I stay on Sendgrid because it's less expensive. But I might change some day. – Marie Dm Sep 27 '20 at 09:03

1 Answers1

0

I found a solution to the problem with Mailchimp, hence this will now answer the issue exactly. As of Sept 2020, it's not possible to achieve this via the SendGrid API but you can do potentially something similar like I did. My answer is based on this post here: https://stackoverflow.com/a/53096852/4163583

  1. Sign up and create a Mailchimp account
  2. Create an Audience who should receive your emails (they will call this later list in their API docs FYI)
  3. Go to Campaigns -> Email Templates and create a new Template with "Paste in code"

enter image description here

  1. Inside your editor for the template, you can now add some parts with Mailchimp's own language here. Compare my screenshot, but they have a guide here that explains it further.

enter image description here

My goal was to have certain parts designed in the editor and certain areas that I want to fill with dynamic content via the API. Hence I had a few lines at different locations where I will add dynamic code.

  1. Now we can actually write code. What I am doing is basically the following: First load the data from the DB, then bring it in the right formal and create HTML code that I will inject into the Mailchimp template at the right place, then create + update the campaign with the dynamic content and send it out. Let's go:

export const sendOutDailyDigest = async () => {
  const campaignDetails = {
    // Details about your campagin e.g. subject or sender
  };
  try {
    // I'm retrieving here my data from the DB
    const data: any = await retrieveDataForDailyDigest();
    // This is the data that I want to inject in the template
    const dynamicData = {
      producthunt: fillProducthuntProductTable(data.producthunt),
      betalist: fillBetalistProductTable(data.betalist),
      crunchbase: fillCrunchbaseTable(data.crunchbase),
      yesterdayDate: `Yesterday's Highlights`,
    };
    // Sending out the campaign
    await sendOutMailchimpCampaign(
      campaignDetails,
      MAILCHIMP_LIST,
      MC_SEGMENT_DAILY_DIGEST,
      DAILY_DIGEST_TEMPLATE_ID,
      dynamicData
    );
  } catch (error) {
    console.error("Error during sendOutDailyDigest ", error);
  }
};
  1. To create the HTML that will be later injected I have for example a function lik this here:

export const fillProducthuntProductTable = (products: any) => {
  let tablebody = "";
  products.forEach((p: any) => {
    const productRow = `
          <tr>
              <td><a href="${p.url}" alt="">${p.title}</a></td>
              <td>${p.description}</td>
              <td>${p.category}</td>
              <td>${p.upvotes}</td>
          </tr>
          `;
    tablebody += productRow;
  });
  return `
        <table style="width:100%">
            <tr>
                <th>Startup Name</th>
                <th>Description</th>
                <th>Category</th>
                <th>Upvotes</th>
            </tr>
            ${tablebody}
        </table>`;
};
  1. Let's look what I'm doing in sendOutMailchimpCampaign. The important calls are the create campaign, update campaign content, and send out campaign.

const sendOutMailchimpCampaign = async (
  campaignDetails: any,
  mailchimpList: string,
  mailchimpSegment: number,
  templateId: number,
  dynamicData: any
) => {
  try {
    // Creating a campaign
    const campaign: any = await createMailchimpCampaign(
      mailchimpList,
      mailchimpSegment,
      campaignDetails
    );
    // Updating the campaign with the dynamic data that I fetched from the db
    await updateCampaignContent(campaign.id, templateId, dynamicData);

    // Sending out the campaign
    return new Promise((resolve, reject) => {
      mailchimp.campaigns
        .send(campaign.id)
        .then((_: any) => {
          console.log("Successfully sent out daily digest.");
          resolve();
        })
        .catch((error: any) => {
          console.log("Error sending out daily digest: ", error);
          reject();
        });
    });
  } catch (error) {
    console.error("Error during sendOutMailchimpCampaign ", error);
  }
};

// Function for creating the above mentioned campaign like this: 

export const createMailchimpCampaign = (
  mailchimpList: string,
  mailchimpSegment: number,
  campaignDetails: any
) => {
  return new Promise((resolve, reject) => {
    mailchimp.campaigns
      .create({
        type: "regular",
        recipients: {
          list_id: mailchimpList,
          segment_opts: {
            saved_segment_id: mailchimpSegment,
          },
        },
        settings: campaignDetails,
      })
      .then((response: any) => {
        resolve(response);
      })
      .catch((error: any) => {
        console.log(error);
        reject(error);
      });
  });
};

// Function for updating the campaign with the dynamic content

const updateCampaignContent = async (
  campaignId: number,
  templateId: number,
  dynamicData: any
) => {
  return new Promise((resolve, reject) => {
    mailchimp.campaigns
      .setContent(campaignId, {
        template: {
          id: templateId,
          sections: dynamicData,
        },
      })
      .then((_: any) => {
        resolve();
      })
      .catch((error: any) => {
        console.log(error);
        reject(error);
      });
  });
};

That's it. The code is in TypeScript, hence I apologize or all Javascript friends here but I guess you can translate it. I hope this helps someone and saves them some hours, even though it's not the solution in SendGrid.

makle
  • 1,237
  • 1
  • 15
  • 21