Sending my first webmention from scratch

DezeStijn 4 mins read

Series: Joining the Fediverse

Hello, is this mic on?

Sending a Webmention

This page is written purely to test out whether I can send out Webmentions.

Following @aaronpk’s awesome tutorial, I put toghether the Webmention below as a test to verify whether I can send out Webmentions.

@aaronpk

Testing my very first webmention. Thank you @aaronpk for your tutorial!

To send a Webmention, it actually suffices to have a link to a site that supports receiving Webmentions. However, using some microformatting with h-entry and h-card, you can add context to them. In this case the Webmention is formatted as a reply but it could just as well be formatted as a like or a repost.

Hugo Shortcode

To make my future life easier, I created a Hugo shortcode as well.

The Webmention above is then constructed as follows:

{{ < webmention type="reply" href="https://aaronparecki.com/2018/06/30/11/your-first-webmention" name="@aaronpk" > }}
Testing my very first webmention. Thank you @aaronpk for your tutorial!
{{ < /webmention > }}

Note:
I had to add spaces between {{ < and > }} to prevent Hugo from rendering the shortcode. So make sure you remove them if you’re copy/pasting my example.

<!-- /layouts/shortcodes/webmention.html -->
<div class="webmention h-entry">
    {{ $author := lower (or ($.Page.Params.author) ($.Site.Params.author.name)) }}
    {{ with index $.Site.Data.authors $author }}
        {{ $name := .name }}
        <div class="wm-author u-author h-card">
            {{- with .avatar -}}
                {{ with resources.Get . }}
                    {{ with .Resize "40x webp" }}
                        <img src="{{ .RelPermalink }}" alt="Avatar of {{ $name }}" class="wm-photo u-photo" />
                    {{ end }}
                {{ end }}
            {{- end -}}
            <a href="{{ $.Site.BaseURL }}" class="wm-name u-url p-name">{{ $name }}</a>
        </div>
    {{ end }}
    {{ if .Get "type" }}
        {{ $type := .Get "type" }}
        {{ $href := .Get "href" }}
        {{ $name := .Get "name" }}
        <span class="wm-response">
            <span class="wi wi-{{ $type }}">{{ partial "svg" $type }}</span>
            {{ if eq $type "reply" }}
                <a class="u-in-reply-to" href="{{ $href }}" title="In reply to {{ $name }}">{{ $name }}</a>
            {{ else if eq $type "like" }}
                <a class="u-like-of" href="{{ $href }}" title="Liking {{ $name }}">{{ $name }}</a>
            {{ else if eq $type "repost" }}
                <a class="u-repost-of" href="{{ $href }}" title="Repost of {{ $name }}">{{ $name }}</a>
            {{ else if eq $type "rsvp" }}
                {{ $rsvp := .Get "rsvp" }}
                <data class="p-rsvp" value="{{ $rsvp }}">
                    <a href="{{ $href }}" title="RSVP to {{ $name }}">{{ $rsvp }}</a>
                </data>
            {{ end }}
        </span>
    {{ end }}
    <p class="wm-content e-content">
        {{- .Inner -}}
    </p>
    <p>
        <a href="{{ $.Page.Permalink }}" class="wm-timestamp u-url">
            <time class="dt-published" datetime="{{ $.Page.Params.Date | time.Format "2006-01-02T15:04:05Z0700" }}">{{ $.Page.Params.Date | time.Format ":date_medium" }}</time>
        </a>
    </p>
</div>

Notice how I’m referencing $.Site.Data.authors in the shortcode. This is using a JSON data file, which looks like this:

// /data/authors.json
{
    "dezestijn": {
        "name": "DezeStijn",
        "avatar": "/img/avatars/dezestijn.png"
    }
}

Note:
Don’t forget to add the avatar image to the /assets/img/avatars folder.

In the shortcode, I’m also referencing a partial named svg. This partial makes it easier to include inline SVGs.

To use it, store the SVGs you want in /assets/icons/.

{{ $svg := . }}
{{ return ((printf "/assets/icons/%s.svg" $svg | readFile) | safeHTML) }}

You can then use the following template to include /assets/icons/reply.svg:

{{ partial "svg" "reply" }}

Automated sending

To then actually notify other sites of the fact that I’m mentioning them, I’m using the @remy/webmention package in a GitHub workflow:

---
name: Notify
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

on:
  # Wait for the build&deploy workflow to finish
  workflow_run:
    workflows: ["build-deploy"]
    branches: [main]
    types: 
      - completed
  workflow_dispatch: {}

jobs:
  publish:
    runs-on: ubuntu-latest
    # Only run if build&deploy was successful
    if: ${{ github.event.workflow_run.conclusion == 'success' }}
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Cache node modules
        uses: ./.github/actions/cache
        with:
          path: |
            ~/.npm
            node_modules            
          hash: ${{ hashFiles('*package-lock.json') }}
          name: cache-node-modules-v1
      
      - name: Setup NodeJS
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install Node.js dependencies
        run: npm install
      
      - name: Send WebMentions
        run: npm run wm:send
{
  "name": "sequr.be",
  // ...
  "engines": {
    "node": "20.x"
  },
  "dependencies": {
    "@remy/webmention": "^1.5.0",
    "npx": "^10.2.2"
  },
  "scripts": {
    // Take last item from Atom feed and send webmentions
    "wm:send": "npx webmention public/atom.xml --limit 1 --send"
  }
}

And if all goes well, this post should now be listed as a reply/comment on Aaron’s blog post.