How to Send an HTML Table to Sendgrid via API Connector

So this has consumed me a bit lately. Because SendGrid has no javascript, if you want an HTML table you have to send it to them. Here’s how I did it.

The table body and table head can be built in Sendgrid but the rows and data in the rows are dynamic - shopping cart items for different shopping carts.

I already have a Workflow on a List that writes the individual items in the cart into a table called, funny enough, cartItems. And I write about 10 elements for each item into the database.

Each record is a row. Just like the rows of the HTML table.

So I added text column and in the column I added the HTML for a row and columns and then used the dynamic content from the list to populate the table row. I even styled the row and cells a bit (no quotes allowed, no &quot’ but single quotes are read)…

The last step in the workflow for each iteration is to update a cart record with the HTML element I just created. Since the workflow is on a list, each time it looped through, I appended the new record to the old and saved it. At then, the table details were in one text column on the cart table.

I then select that in a workflow using the API Connector and SendGrid and template I created, I sent the JSON body to SendGrid and mapped elements into the email and then one large element into the table body.

You can see the text I used to create the HTML table data, the JSON load, and the final email to the customer.

Kind of cool.


![Screen Shot 2025-01-17 at 4.18.14 PM|501x500]
(upload://khX8yAryQPEXEMgygciHIP04dwL.png)

{
  "from": {
    "email": "deals@iwantthat.io",
    "name": "I Want That!"
  },
  "personalizations": [
    {
      "to": [
        {
          "email": "<consumerEmail>"
        }
      ],
              "bcc": [
        {
          "email": "deals@iwantthat.io"
        }
      ],
      "dynamic_template_data": {
        "consumer": {
          "consumerName": "<consumerName>",
              
          "consumerFirstName": "<firstName>",
              
          "consumerEmail": "<consumerEmail>",
              
          "consumerMobile": "<consumerMobile>",
              
          "consumerPostalCode": "<consumerPostalCode>"
        },
        "offer": {
          "offerCreateDate": "<offerCreateDate>",
              
          "offerPrice": "<offerPrice>",
              
          "offerDiscountPrice": "<offerDiscountPrice>",
              
          "offerStatus": "<offerStatus>"
              
        },
        "response" : {
              
          "offerAcceptedDate" : "<offerAcceptedDate>",
          
          "offerAcceptedPrice" : "<offerAcceptedPrice>",
          
          "offerAcceptedDiscount" : "<offerAcceptedDiscount>",
              
          "offerAcceptedUnits" : "<offerAcceptedUnits>",
           
          "offerAcceptedItems" : "<offerAcceptedItems>",
          
          "offerExpiryMinutes": "<offerExpiryMinutes>",
              
          "offerExpiryTime": "<offferExpiryTime>"            
              
        },
              
        "cart": {
          "cartTotalPrice": "<cartTotalPrice>",
              
          "checkoutURL": "<checkoutURL>",
              
          "cartItemsCount": "<cartItemsCount>",
              
          "cartUnitsSum": "<cartUnitsSum>"
              
        },
       "store": {
          "storeBrand": "<storeBrand>"
        },
           "cartItems" :{
              "cartProductsList" : ["<cartProductsList>"]
        }
      }
    }
  ],
        "template_id": "<sgTemplateId>"
}

for simple looping you can also use search for list formated as text

this way it doesn’t have to loop in a workflow it just prints the text for each record (you can add data from each record into the format as text)

format as text is fine for simple data (like your table) but you can’t go too nested with it. and it can be a bit of a pain for complex setups.

another approach I’ve used is to offload the html creation to something like make.com where it is much easier to build and maintain the html (just send the data over, then send the html back - set it as an api data call)