{"id":22433951,"url":"https://github.com/jericho/stronggrid","last_synced_at":"2025-05-16T14:04:45.803Z","repository":{"id":39596751,"uuid":"71071386","full_name":"Jericho/StrongGrid","owner":"Jericho","description":"Strongly typed library for the entire SendGrid v3 API, including webhooks","archived":false,"fork":false,"pushed_at":"2025-05-08T00:28:33.000Z","size":3435,"stargazers_count":191,"open_issues_count":14,"forks_count":39,"subscribers_count":14,"default_branch":"develop","last_synced_at":"2025-05-16T14:04:31.793Z","etag":null,"topics":["email","sendgrid","sendgrid-api","sendgrid-api-wrapper","sendgrid-inbound-webhook-parser"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Jericho.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2016-10-16T19:12:53.000Z","updated_at":"2025-05-08T00:28:37.000Z","dependencies_parsed_at":"2023-11-14T17:27:20.372Z","dependency_job_id":"84d7182d-c1bb-4790-a997-f6bb5de06773","html_url":"https://github.com/Jericho/StrongGrid","commit_stats":{"total_commits":1387,"total_committers":19,"mean_commits":73.0,"dds":0.03100216294160063,"last_synced_commit":"a8c3d08d9d6843f066e9047288ab0cbbef7c4327"},"previous_names":[],"tags_count":128,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Jericho%2FStrongGrid","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Jericho%2FStrongGrid/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Jericho%2FStrongGrid/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Jericho%2FStrongGrid/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Jericho","download_url":"https://codeload.github.com/Jericho/StrongGrid/tar.gz/refs/heads/develop","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254544146,"owners_count":22088807,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["email","sendgrid","sendgrid-api","sendgrid-api-wrapper","sendgrid-inbound-webhook-parser"],"created_at":"2024-12-05T22:17:02.813Z","updated_at":"2025-05-16T14:04:45.762Z","avatar_url":"https://github.com/Jericho.png","language":"C#","readme":"# StrongGrid\n\n[![Discussions at https://github.com/Jericho/StrongGrid/discussions](https://img.shields.io/badge/discuss-here-lightgrey)](https://github.com/Jericho/StrongGrid/discussions)\n[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2FJericho%2FStrongGrid.svg?type=shield)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2FJericho%2FStrongGrid?ref=badge_shield)\n[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://jericho.mit-license.org/)\n[![Sourcelink](https://img.shields.io/badge/sourcelink-enabled-brightgreen.svg)](https://github.com/dotnet/sourcelink)\n\n[![Build status](https://ci.appveyor.com/api/projects/status/4c0c37snfwkhgpos?svg=true)](https://ci.appveyor.com/project/Jericho/stronggrid)\n[![Tests](https://img.shields.io/appveyor/tests/jericho/stronggrid/master.svg)](https://ci.appveyor.com/project/jericho/stronggrid/build/tests)\n[![Coverage Status](https://coveralls.io/repos/github/Jericho/StrongGrid/badge.svg?branch=master)](https://coveralls.io/github/Jericho/StrongGrid?branch=master)\n[![CodeFactor](https://www.codefactor.io/repository/github/jericho/stronggrid/badge)](https://www.codefactor.io/repository/github/jericho/stronggrid)\n\n| Release Notes| NuGet (stable) | MyGet (prerelease) |\n|--------------|----------------|--------------------|\n| [![GitHub release](https://img.shields.io/github/release/jericho/stronggrid.svg)](https://github.com/Jericho/StrongGrid/releases) | [![NuGet Version](https://img.shields.io/nuget/v/StrongGrid.svg)](https://www.nuget.org/packages/StrongGrid/) | [![MyGet Pre Release](https://img.shields.io/myget/jericho/vpre/StrongGrid.svg)](https://myget.org/gallery/jericho) |\n\n## About\n\nStrongGrid is a strongly typed library for SendGrid's v3 API.\n\nIt started out in February 2016 as a fork of SendGrid's own library. At the time, the SendGrid C# client for their API extensively used the `dynamic` type which was very inconvenient and made it very difficult for developers. Furthermore, their C# client only covered the `mail` end point but did not allow access to other end points in their `email marketing` API such as creating lists and segments, importing contacts, etc. I submited a [pull request](https://github.com/sendgrid/sendgrid-csharp/pull/211) to SendGrid in March 2016 but it was not accepted and eventually closed in June 2016.\n\nIn October 2016 I decided to release this library as a nuget package since SendGrid's library was still using `dynamic` and lacking strong typing. As of February 14, 2017 `dynamic` was removed from [SendGrid's official csharp library](https://github.com/sendgrid/sendgrid-csharp) and support for .Net Standard was added.\n\nStrongGrid includes a client that allows you to interact with all the \"resources\" in the SendGrid API (e.g.: send an email, manage lists, contacts and segments, search for contacts matching criteria, create API keys, etc.).\n\nStrongGrid also includes a parser for webhook sent from SendGrid to your own WebAPI. This parser supports the two types of webhooks that SendGrid can post to your API: the Event Webhook and the Inbound Parse Webhook. \n\nSince November 2017, StrongGrid also includes a \"warmup engine\" that allows you to warmup IP addresses using a custom schedule.\n\nIf you need information about how to setup the SendGrid webhooks, please consult the following resources:\n- [Webhooks Overview](https://sendgrid.com/docs/API_Reference/Webhooks/debug.html)\n- [Guide to debug webhooks](https://sendgrid.com/docs/API_Reference/Webhooks/index.html)\n- [Setting up the inbound parse webhook](https://sendgrid.com/docs/Classroom/Basics/Inbound_Parse_Webhook/setting_up_the_inbound_parse_webhook.html)\n\n## Installation\n\nThe easiest way to include StrongGrid in your C# project is by adding the nuget package to your project:\n\n```\nPM\u003e Install-Package StrongGrid\n```\n\nOnce you have the StrongGrid library properly referenced in your project, add the following namespace:\n\n```\nusing StrongGrid;\n```\n\n\n## .NET framework suport\n\nStrongGrid supports the `4.8`, `6.0` and `7.0` .NET frameworks as well as any framework supporting `.NET Standard 2.1` (which includes `.NET Core 3.x` and `ASP.NET Core 3.x`).\n\n## Usage\n\n### Client\nYou declare your client variable like so:\n```csharp\nvar apiKey = \"... your api key...\";\nvar strongGridClient = new StrongGrid.Client(apiKey);\n```\n\nIf you need to use a proxy, you can pass it to the Client:\n```csharp\nvar apiKey = \"... your api key...\";\nvar proxy = new WebProxy(\"http://myproxy:1234\");\nvar strongGridClient = new StrongGrid.Client(apiKey, proxy);\n```\n\nOne of the most common scenarios is to send transactional emails. \n\nHere are a few examples:\n```csharp\n// Send an email to a single recipient\nvar messageId = await strongGridClient.Mail.SendToSingleRecipientAsync(to, from, subject, html, text).ConfigureAwait(false);\n\n// Send an email to multiple recipients\nvar messageId = await strongGridClient.Mail.SendToMultipleRecipientsAsync(new[] { to1, to2, to3 }, from, subject, html, text).ConfigureAwait(false);\n\n// Include attachments when sending an email\nvar attachments = new[]\n{\n\tAttachment.FromLocalFile(@\"C:\\MyDocuments\\MySpreadsheet.xlsx\"),\n\tAttachment.FromLocalFile(@\"C:\\temp\\Headshot.jpg\")\n};\nvar messageId = await strongGridClient.Mail.SendToSingleRecipientAsync(to, from, subject, html, text, attachments: attachments).ConfigureAwait(false);\n```\n\nYou have access to numerous 'resources' (such as Contacts, Lists, Segments, Settings, SenderAuthentication, etc) off of the Client and each resource offers several methods such as retrieve, create, update, delete, etc. \n\nHere are a few example:\n```csharp\n// Import a new contact or update existing contact if a match is found\nvar importJobId = await client.Contacts.UpsertAsync(email, firstName, lastName, addressLine1, addressLine2, city, stateOrProvince, country, postalCode, alternateEmails, customFields, null, cancellationToken).ConfigureAwait(false);\n\n// Import several new contacts or update existing contacts when a match is found\nvar contacts = new[]\n{\n\tnew Models.Contact(\"dummy1@hotmail.com\", \"John\", \"Doe\"),\n\tnew Models.Contact(\"dummy2@hotmail.com\", \"John\", \"Smith\"),\n\tnew Models.Contact(\"dummy3@hotmail.com\", \"Bob\", \"Smith\")\n};\nvar importJobId = await client.Contacts.UpsertAsync(contacts, null, cancellationToken).ConfigureAwait(false);\n\n// Send an email\nawait strongGridClient.Mail.SendToSingleRecipientAsync(to, from, subject, htmlContent, textContent);\n\n// Retreive all the API keys in your account\nvar apiKeys = await strongGridClient.ApiKeys.GetAllAsync();\n\n// Add an email address to a suppression group\nawait strongGridClient.Suppressions.AddAddressToUnsubscribeGroupAsync(groupId, \"test1@example.com\");\n\n// Get statistics between the two specific dates\nvar globalStats = await strongGridClient.Statistics.GetGlobalStatisticsAsync(startDate, endDate);\n\n// Create a new email template\nvar template = await strongGridClient.Templates.CreateAsync(\"My template\");\n```\n\n### Dynamic templates\nIn August 2018, SendGrid released a new feature in their API that allows you to use the [Handlebars syntax](https://sendgrid.com/docs/User_Guide/Transactional_Templates/Using_handlebars.html) to specify merge fields in your content. Using this powerful new feature in StrongGrid is very easy.\n\nFirst, you must specify `TemplateType.Dynamic` when creating a new template like in this example:\n\n```csharp\nvar dynamicTemplate = await strongGridClient.Templates.CreateAsync(\"My dynamic template\", TemplateType.Dynamic).ConfigureAwait(false);\n```\n\nSecond, you create a version of your content where you use the Handlebars syntax to define the merge fields and you can also specify an optional \"test data\" that will be used by the SendGrid UI to show you a sample. Rest assured that this test data will never be sent to any recipient. The following code sample demonstrates creating a dynamic template version containing [simple substitution](https://sendgrid.com/docs/User_Guide/Transactional_Templates/Using_handlebars.html#-Substitution) for `CreditBalance`, [deep object replacements](https://sendgrid.com/docs/User_Guide/Transactional_Templates/Using_handlebars.html#-Deep-object-replacement) for `Customer.first_name` and `Customer.last_name` and an [iterator](https://sendgrid.com/docs/User_Guide/Transactional_Templates/Using_handlebars.html#-Iterations) that displays information about multiple orders.\n\n```csharp\nvar subject = \"Dear {{Customer.first_name}}\";\nvar htmlContent = @\"\n\t\u003chtml\u003e\n\t\t\u003cbody\u003e\n\t\t\tHello {{Customer.first_name}} {{Customer.last_name}}. \n\t\t\tYou have a credit balance of {{CreditBalance}}\u003cbr/\u003e\n\t\t\t\u003col\u003e\n\t\t\t{{#each Orders}}\n\t\t\t\t\u003cli\u003eYou ordered: {{this.item}} on: {{this.date}}\u003c/li\u003e\n\t\t\t{{/each}}\n\t\t\t\u003c/ol\u003e\n\t\t\u003c/body\u003e\n\t\u003c/html\u003e\";\nvar textContent = \"... this is the text content ...\";\nvar testData = new\n{\n\tCustomer = new\n\t{\n\t\tfirst_name = \"aaa\",\n\t\tlast_name = \"aaa\"\n\t},\n\tCreditBalance = 99.88,\n\tOrders = new[]\n\t{\n\t\tnew { item = \"item1\", date = \"1/1/2018\" },\n\t\tnew { item = \"item2\", date = \"1/2/2018\" },\n\t\tnew { item = \"item3\", date = \"1/3/2018\" }\n\t}\n};\nawait strongGridClient.Templates.CreateVersionAsync(dynamicTemplate.Id, \"Version 1\", subject, htmlContent, textContent, true, EditorType.Code, testData).ConfigureAwait(false);\n```\n\nFinally, you can send an email to a recipient and specify the dynamic data that applies to them like so:\n\n```csharp\nvar dynamicData = new\n{\n\tCustomer = new\n\t{\n\t\tfirst_name = \"Bob\",\n\t\tlast_name = \"Smith\"\n\t},\n\tCreditBalance = 56.78,\n\tOrders = new[]\n\t{\n\t\tnew { item = \"shoes\", date = \"2/1/2018\" },\n\t\tnew { item = \"hat\", date = \"1/4/2018\" }\n\t}\n};\nvar to = new MailAddress(\"bobsmith@hotmail.com\", \"Bob Smith\");\nvar from = new MailAddress(\"test@example.com\", \"John Smith\");\nvar messageId = await strongGridClient.Mail.SendToSingleRecipientAsync(to, from, dynamicTemplate.Id, dynamicData).ConfigureAwait(false);\n```\n\n\n### Webhook Parser\n \nHere's a basic example of a .net 6.0 API controller which parses the webhook from SendGrid:\n```csharp\nusing Microsoft.AspNetCore.Mvc;\nusing StrongGrid;\n\nnamespace WebApplication1.Controllers\n{\n\t[Route(\"api/[controller]\")]\n\t[ApiController]\n\tpublic class SendGridWebhooksController : ControllerBase\n\t{\n\t\t[HttpPost]\n\t\t[Route(\"Events\")]\n\t\tpublic async Task\u003cIActionResult\u003e ReceiveEvents()\n\t\t{\n\t\t\tvar parser = new WebhookParser();\n\t\t\tvar events = await parser.ParseEventsWebhookAsync(Request.Body).ConfigureAwait(false);\n\n\t\t\t// ... do something with the events ...\n\n\t\t\treturn Ok();\n\t\t}\n\n\t\t[HttpPost]\n\t\t[Route(\"InboundEmail\")]\n\t\tpublic async Task\u003cIActionResult\u003e ReceiveInboundEmail()\n\t\t{\n\t\t\tvar parser = new WebhookParser();\n\t\t\tvar inboundEmail = await parser.ParseInboundEmailWebhookAsync(Request.Body).ConfigureAwait(false);\n\n\t\t\t// ... do something with the inbound email ...\n\n\t\t\treturn Ok();\n\t\t}\n    }\n}\n```\n\n### Parsing a signed webhook\n\nSendGrid has a feature called `Signed Event Webhook Requests` which you can enable under `Settings \u003e Mail Settings \u003e Event Settings` when logged in your SendGrid account. When this feature is enabled, SendGrid includes additional information with each webhook that allows you to verify that this webhook indeed originated from SendGrid and therefore can be trusted. Specifically, the webhook will include a \"signature\" and a \"timestamp\" and you must use these two values along with a public key that SendGrid generated when you enabled the feature to validate the data being submited to you. Please note that SendGrid sometimes refers to this value as a \"verification key\". In case you are curious and want to know more about the intricacies of validating the data, I invite you to read SendGrid's [documentation on this topic](https://sendgrid.com/docs/for-developers/tracking-events/getting-started-event-webhook-security-features/).\n\nHowever, if you want to avoid learning how to perform the validation and you simply want this validation to be conveniently performed for you, StrongGrid can help! The `WebhookParser` class has a method called `ParseSignedEventsWebhookAsync`which will automatically validate the data and throw a security exception if validation fails. If the validation fails, you should consider the webhook data to be invalid. Here's how it works:\n\n```csharp\nusing Microsoft.AspNetCore.Mvc;\nusing StrongGrid;\nusing System.Security;\n\nnamespace WebApplication1.Controllers\n{\n\t[Route(\"api/[controller]\")]\n\t[ApiController]\n\tpublic class SendGridWebhooksController : ControllerBase\n\t{\n\t\t[HttpPost]\n\t\t[Route(\"SignedEvents\")]\n\t\tpublic async Task\u003cIActionResult\u003e ReceiveSignedEvents()\n\t\t{\n\t\t\t// Get your public key\n\t\t\tvar apiKey = \"... your api key...\";\n\t\t\tvar strongGridClient = new StrongGrid.Client(apiKey);\n\t\t\tvar publicKey = await strongGridClient.WebhookSettings.GetSignedEventsPublicKeyAsync().ConfigureAwait(false);\n\n\t\t\t// Get the signature and the timestamp from the request headers\n\t\t\tvar signature = Request.Headers[WebhookParser.SIGNATURE_HEADER_NAME]; // SIGNATURE_HEADER_NAME is a convenient constant provided so you don't have to remember the name of the header\n\t\t\tvar timestamp = Request.Headers[WebhookParser.TIMESTAMP_HEADER_NAME]; // TIMESTAMP_HEADER_NAME is a convenient constant provided so you don't have to remember the name of the header\n\n\t\t\t// Parse the events. The signature will be automatically validated and a security exception thrown if unable to validate\n\t\t\ttry\n\t\t\t{\n\t\t\t\tvar parser = new WebhookParser();\n\t\t\t\tvar events = await parser.ParseSignedEventsWebhookAsync(Request.Body, publicKey, signature, timestamp).ConfigureAwait(false);\n\n\t\t\t\t// ... do something with the events...\n\t\t\t}\n\t\t\tcatch (SecurityException e)\n\t\t\t{\n\t\t\t\t// ... unable to validate the data...\n\t\t\t}\n\n\t\t\treturn Ok();\n\t\t}\n\t}\n}\n```\n\n### Warmup Engine\n\nSendGrid already provides a way to warm up ip addresses but you have no control over this process. StrongGrid solves this issue by providing you a warmup engine that you can tailor to your needs.\n\n#### Typical usage\n\n```csharp\n// Prepare the warmup engine\nvar poolName = \"warmup_pool\";\nvar dailyVolumePerIpAddress = new[] { 50, 100, 500, 1000 };\nvar resetDays = 1; // Should be 1 if you send on a daily basis, should be 2 if you send every other day, should be 7 if you send on a weekly basis, etc.\nvar warmupSettings = new WarmupSettings(poolName, dailyVolumePerIpAddress, resetDays);\nvar warmupEngine = new WarmupEngine(warmupSettings, client);\n\n// This is a one-time call to create the IP pool that will be used to warmup the IP addresses\nvar ipAddresses = new[] { \"168.245.123.132\", \"168.245.123.133\" };\nawait warmupEngine.PrepareWithExistingIpAddressesAsync(ipAddresses, CancellationToken.None).ConfigureAwait(false);\n\n// Send emails using any of the following methods\nvar result = warmupEngine.SendToSingleRecipientAsync(...);\nvar result = warmupEngine.SendToMultipleRecipientsAsync(...);\nvar result = warmupEngine.SendAsync(...);\n```\n\nThe `Send...` methods return a `WarmupResult` object that will tell you whether the process is completed or not, and will also give you the messageId of the email sent using the IP pool (if applicable) and the messageId of the email sent using the default IP address (which is not being warmed up).\nThe WarmupEngine will send emails using the IP pool until the daily volume limit is achieved and any remaining email will be sent using the default IP address.\nAs you get close to your daily limit, it's possible that the Warmup engine may have to split a given \"send\" into two messages: one of which is sent using the ip pool and the other one sent using the default ip address.\nLet's use an example to illustrate: let's say that you have 15 emails left before you reach your daily warmup limit and you try to send an email to 20 recipients. In this scenario the first 15 emails will be sent using the warmup ip pool and the remaining 5 emails will be sent using the default ip address.\n\n#### More advanced usage\n\n**Recommended daily volume:** If you are unsure what daily limits to use, [SendGrid has provided a recommended schedule](https://sendgrid.com/docs/assets/IPWarmupSchedule.pdf) and StrongGrid provides a convenient method to use the recommended schedule tailored to the number of emails you expect to send in a typical day.\nAll you have to do is come up with a rough estimate of your daily volume and StrongGrid can configure the appropriate warmup settings.\nHere's an example:\n\n```csharp\nvar poolName = \"warmup_pool\";\nvar estimatedDailyVolume = 50000; // Should be your best guess: how many emails you will be sending in a typical day\nvar resetDays = 1; // Should be 1 if you send on a daily basis, should be 2 if you send every other day, should be 7 if you send on a weekly basis, etc.\nvar warmupSettings = WarmupSettings.FromSendGridRecomendedSettings(poolName, estimatedDailyVolume, resetDays);\n```\n\n**Progress repository:** By default StrongGrid's WarmupEngine will write progress information in a file on your computer's `temp` folder but you can override this settings. \nYou can change the folder where this file is saved and you can also decide to use a completely different repository. Out of the box, StrongGrid provides `FileSystemWarmupProgressRepository` and `MemoryWarmupProgressRepository`.\nIt also provides an interface called `IWarmupProgressRepository` which allows you to write your own implementation to save the progress data to a location more suitable to you such as a database, Azure, AWS, etc.\nPlease note that `MemoryWarmupProgressRepository` in intended to be used for testing and we don't recommend using it in production. The main reason for this recommendation is that the data is stored in memory and is lost when your computer is restarted.\nThis means that your warmup process would start all over from day 1 each time you computer is rebooted.\n\n```csharp\n// You select one of the following repositories available out of the box:\nvar warmupProgressRepository = new MemoryWarmupProgressRepository();\nvar warmupProgressRepository = new FileSystemWarmupProgressRepository();\nvar warmupProgressRepository = new FileSystemWarmupProgressRepository(@\"C:\\temp\\myfolder\\\");\nvar warmupEngine = new WarmupEngine(warmupSettings, client, warmupProgressRepository);\n```\n\n**Purchase new IP Addresses:** You can purchase new IP addresses using SendGrid' UI, but StrongGrid's WarmupEngine makes it even easier.\nRather than invoking `PrepareWithExistingIpAddressesAsync` (as demonstrated previously), you can invoke `PrepareWithNewIpAddressesAsync` and StrongGrid will take care of adding new ip addresses to your account and add them to a new IP pool ready for warmup.\nAs a reminder, please note that the `PrepareWithExistingIpAddressesAsync` and `PrepareWithNewIpAddressesAsync` should only be invoked once.\nInvoking either method a second time would result in an exception due to the fact that the IP pool has already been created.\n\n```csharp\nvar howManyAddresses = 2; // How many ip addresses do you want to purchase?\nvar subusers = new[] { \"your_subuser\" }; // The subusers you authorize to send emails on the new ip addresses\nawait warmupEngine.PrepareWithNewIpAddressesAsync(howManyAddresses, subusers, CancellationToken.None).ConfigureAwait(false);\n```\n\n**End of warmup process:** When the process is completed, the IP pool is deleted and the warmed up IP address(es) are returned to the default pool. You can subsequently invoke the `strongGridClient.Mail.SendAsync(...)` method to send your emails.\n\n## License\n[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2FJericho%2FStrongGrid.svg?type=large)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2FJericho%2FStrongGrid?ref=badge_large)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjericho%2Fstronggrid","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjericho%2Fstronggrid","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjericho%2Fstronggrid/lists"}