|
| 1 | +# Blog Crossposting Automation |
| 2 | + |
| 3 | +Are you a blog writer? Hate cross-posting your content across the web? You're in luck! |
| 4 | + |
| 5 | +This solution will hook into your blog creation process and automatically cross-post your content for you to Medium, Dev.to, and Hashnode! |
| 6 | + |
| 7 | +Deploy into your AWS account and type away! |
| 8 | + |
| 9 | +For a full summary of this solution [please refer to this blog post](https://www.readysetcloud.io/blog/allen.helton/how-i-built-a-serverless-automation-to-cross-post-my-blogs/) by [Allen Helton](https://twitter.com/allenheltondev). |
| 10 | + |
| 11 | +## Prerequisites |
| 12 | + |
| 13 | +For cross-posts to work successfully, there are a few prereqs that must be met in your setup. |
| 14 | + |
| 15 | +* Your blog post must be written in [markdown](https://en.wikipedia.org/wiki/Markdown). |
| 16 | +* Content is checked into a repository in GitHub |
| 17 | +* You have an application in [AWS Amplify](https://aws.amazon.com/amplify/) that has a runnable CI pipeline |
| 18 | +* Blog posts have front matter in the format outlined in the [Blog Metadata](#blog-metadata) section |
| 19 | + |
| 20 | +## How It Works |
| 21 | + |
| 22 | + |
| 23 | + |
| 24 | +The cross posting process is outlined below. |
| 25 | + |
| 26 | +1. Completed blog post written in markdown is committed to main branch |
| 27 | +2. AWS Amplify CI pipeline picks up changes and runs build |
| 28 | +3. On success, Amplify publishes a `Amplify Deployment Status Change` event to EventBridge, triggering a Lambda function deployed in this stack |
| 29 | +4. The function uses your GitHub PAT to identify and load the blog post content and pass it into a Step Function workflow |
| 30 | +5. The workflow will do an idempotency check, and if it's ok to continue will transform and publish to Medium, Hashnode, and Dev.to in parallel |
| 31 | +6. After publish is complete, the workflow checks if there were any failures. |
| 32 | + * If there was a failure, it sends an email with a link to the execution for debugging |
| 33 | + * On success, it sends an email with links to the published content and updates the idempotency record and article catalog |
| 34 | + |
| 35 | +*Note - If you do not provide a SendGrid API key, you will not receive email status updates* |
| 36 | + |
| 37 | +## Platforms |
| 38 | + |
| 39 | +This solution will take content you create and automatically cross-post it on three platforms: |
| 40 | + |
| 41 | +* [Medium](https://medium.com) |
| 42 | +* [Dev.to](https://dev.to) |
| 43 | +* [Hashnode](https://hashnode.com) |
| 44 | + |
| 45 | + |
| 46 | + |
| 47 | +## Deployment |
| 48 | + |
| 49 | +The solution is built using AWS SAM. To deploy the resources into the cloud you must install the [SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html). |
| 50 | + |
| 51 | +Once installed, run the following commands in the root folder of the solution. |
| 52 | + |
| 53 | +```bash |
| 54 | +sam build --parallel |
| 55 | +sam deploy --guided |
| 56 | +``` |
| 57 | + |
| 58 | +This will walk you through deployment, prompting you for all the parameters necessary for proper use. Below are the parameters you must fill out on deploy. |
| 59 | + |
| 60 | +|Parameter|Description|Required| |
| 61 | +|---------|-----------|--------| |
| 62 | +|TableName|Name of the DynamoDB table to create|No| |
| 63 | +|GSI1|Name of the GSI on the DDB table|No| |
| 64 | +|GitHubPAT|Personal Access Token to load newsletter content from your repository|Yes| |
| 65 | +|GitHubOwner|The GitHub user name that owns the repository for your content|Yes| |
| 66 | +|GitHubRepo|The repository name that contains your content|Yes| |
| 67 | +|AmplifyProjectId|Identifier of the Amplify project that builds your newsletter|Yes| |
| 68 | +|MediumApiKey|API key used to manipulate data in your Medium account|Yes| |
| 69 | +|MediumPublicationId|Identifier of the publication you wish to submit to on Medium|No| |
| 70 | +|MediumAuthorId|Identifier of your user on Medium|Yes if `MediumPublicationId` is not provided| |
| 71 | +|DevApiKey|API key used to manipulate data in your Dev.to account|Yes| |
| 72 | +|DevOrganizationId|Identifier of the organization you wish to submit to on Dev.to|No| |
| 73 | +|HashnodeApiKey|API key used to manipulate data in your Hashnode account|Yes| |
| 74 | +|HashnodePublicationId|Identifier for your blog publication on Hashnode|Yes| |
| 75 | +|HashnodeBlogUrl|Base url of your blog hosted in Hashnode|Yes| |
| 76 | +|BlogBaseUrl|Vase url of your blog on your personal site|Yes| |
| 77 | +|BlogContentPath|Relative path from the root directory to the blog content folder in your GitHub repo|Yes| |
| 78 | +|SendgridApiKey|Api Key of the SendGrid account that will send the status report when cross-posting is complete|No| |
| 79 | +|NotificationEmail|Email address to notify when cross posting is complete|No| |
| 80 | +|SendgridFromEmail|Email address for SendGrid that sends you the status email|No| |
| 81 | + |
| 82 | +## Notification Emails |
| 83 | + |
| 84 | +If you wish to get notification emails on the status of the cross posting, you must use [SendGrid](https://sendgrid.com). SendGrid offers a generous free tier for email messages and is quick to get started. To configure SendGrid to send you emails you must: |
| 85 | + |
| 86 | +* [Create an API key](https://docs.sendgrid.com/ui/account-and-settings/api-keys) |
| 87 | +* [Create a sender](https://docs.sendgrid.com/ui/sending-email/senders) |
| 88 | + |
| 89 | +Once you perform the above actions, you may use the values in the respective deployment variables listed above. |
| 90 | + |
| 91 | +## Replay |
| 92 | + |
| 93 | +In the event the cross-posting does not work, it can be safely retried without worrying about pushing your content multiple times. Each post will update the idempotency DynamoDB record for the cross-posting state machine. This record holds the status (*success/failure*) for each platform. If the article was successfully posted on a platform, it will be skipped on subsequent executions. |
| 94 | + |
| 95 | +## Blog Metadata |
| 96 | + |
| 97 | +Your blog must be written in Markdown for this solution to work appropriately. To save metadata about your post, you can add [front matter](https://gohugo.io/content-management/front-matter/) at the beginning of the file. This solution requires a specific set of metadata in order to function appropriately. |
| 98 | + |
| 99 | +**Example** |
| 100 | +```yaml |
| 101 | +--- |
| 102 | +title: My first blog! |
| 103 | +description: This is the subtitle that is used for SEO and visible in Medium and Hashnode posts. |
| 104 | +image: https://link-to-hero-image.png |
| 105 | +image_attribution: Any attribution required for hero image |
| 106 | +categories: |
| 107 | + - categoryOne |
| 108 | +tags: |
| 109 | + - serverless |
| 110 | + - other tag |
| 111 | +slug: /my-first-blog |
| 112 | +--- |
| 113 | +``` |
| 114 | + |
| 115 | +|Field|Description|Required?| |
| 116 | +|-----|-----------|---------| |
| 117 | +|title|Title of the blog issue |Yes| |
| 118 | +|description| Brief summary of article. This shows up on Hashnode and Medium and is used in SEO previews|Yes| |
| 119 | +|image|Link to the hero image for your article|Yes| |
| 120 | +|image_attribution|Any attribution text needed for your hero image|No| |
| 121 | +|categories|Array of categories. This will be used as tags for Dev and Medium|No| |
| 122 | +|tags|Array of tags. Also used as tags for Dev and Medium|No| |
| 123 | +|slug|Relative url of your post. Used in the article catalog|Yes| |
| 124 | + |
| 125 | +## Article Catalog |
| 126 | + |
| 127 | +One of the neat features provided by this solution is substituting relative urls for the appropriate urls on a given page. For example, if you use a relative url to link to another blog post you've written on your site, this solution will replace that with the cross-posted version. So Medium articles will always point to Medium articles, Hashnode articles will always point to Hashnode, etc... |
| 128 | + |
| 129 | +This is managed for you by the solution. It creates entries for your content in DynamoDB with the following format: |
| 130 | + |
| 131 | +```json |
| 132 | +{ |
| 133 | + "pk": "<article slug>", |
| 134 | + "sk": "article", |
| 135 | + "GSI1PK": "article", |
| 136 | + "GSI1SK": "<title of the post>", |
| 137 | + "links": { |
| 138 | + "url": "<article slug>", |
| 139 | + "devUrl": "<full path to article on dev.to>", |
| 140 | + "mediumUrl": "<full path to article on Medium>", |
| 141 | + "hashnodeUrl": "<full path to article on Hashnode>" |
| 142 | + } |
| 143 | +} |
| 144 | +``` |
| 145 | + |
| 146 | +When transforming your Markdown content, it will load all articles from DynamoDB, use a Regex to match on the article slug in your content, and replace with the url of appropriate site. |
| 147 | + |
| 148 | +If you already have a number of articles and wish to seed the database with the cross references, you will have to compile the data manually and put it in the following format: |
| 149 | + |
| 150 | +```json |
| 151 | +[ |
| 152 | + { |
| 153 | + "title": "<title of article>", |
| 154 | + "devUrl": "<url of article on dev.to>", |
| 155 | + "url": "<relative url of article on your blog>", |
| 156 | + "mediumUrl": "<url of article on medium>", |
| 157 | + "hashnodeUrl": "<url of article on hashnode>" |
| 158 | + } |
| 159 | +] |
| 160 | +``` |
| 161 | + |
| 162 | +Take this data and update the [load-cross-posts](/functions/load-cross-posts/index.js) function to load and handle that data. Run the function manually to seed the data in your database table. |
| 163 | + |
| 164 | +## Embeds |
| 165 | + |
| 166 | +If you are embedding content in your posts, they might not work out of the box. *There is only support for Hugo twitter embeds.* The format of a Hugo Twitter embed is: |
| 167 | + |
| 168 | +``` |
| 169 | +{{<tweet user="" id="">}} |
| 170 | +``` |
| 171 | + |
| 172 | +If you include this in your content, it will be automatically transformed to the appropriate embed style on the appropriate platform. |
| 173 | + |
| 174 | +## Limitations |
| 175 | + |
| 176 | +Below are a list of known limitations: |
| 177 | + |
| 178 | +* Your content must be written in Markdown with front matter describing the blog post. |
| 179 | +* Content must be hosted in GitHub. |
| 180 | +* You are required to post to Dev.to, Medium, and Hashnode. You cannot pick and choose which platforms you want to use. |
| 181 | +* Only Hugo style Twitter embeds are supported. Embeds for other content will not work. |
| 182 | +* This process is triggered on a successful build of an AWS Amplify project. Other triggers are not supported (but can easily be modified to add them). |
| 183 | +* Notifications are limited to sending emails in SendGrid. |
| 184 | +* The only way to deploy the solution is with AWS SAM. |
| 185 | + |
| 186 | +## Contributions |
| 187 | + |
| 188 | +Please feel free to contribute to this project! Bonus points if you can meaningfully address any of the limitations listed above :) |
| 189 | + |
| 190 | +This is an AWS Community Builders project and is meant to help the community. If you see fit, please donate some time into making it better! |
0 commit comments