diff --git a/config/config.exs b/config/config.exs
index d8d5fa0..41e0957 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -61,12 +61,16 @@ config :flop, repo: Core.Repo
 
 config :tesla, adapter: Tesla.Adapter.Mint
 
+mastodon_instance = "https://tech.lgbt"
+
+config :sloanely_but_surely, Core.Syndication, mastodon_instance: mastodon_instance
+
 config :ueberauth, Ueberauth,
   providers: [
     mastodon:
       {Ueberauth.Strategy.Mastodon,
        [
-         instance: "https://tech.lgbt",
+         instance: mastodon_instance,
          client_id: {System, :get_env, ["MASTODON_CLIENT_ID"]},
          client_secret: {System, :get_env, ["MASTODON_CLIENT_SECRET"]},
          scope: "read write push"
diff --git a/lib/core/posts.ex b/lib/core/posts.ex
index cd5e84a..ba4754c 100644
--- a/lib/core/posts.ex
+++ b/lib/core/posts.ex
@@ -85,9 +85,14 @@ defmodule Core.Posts do
   end
 
   def publish_post(%Schema.Post{} = post, published_at \\ DateTime.utc_now()) do
-    post
-    |> Post.publish_changeset(published_at)
-    |> Core.Repo.update()
+    with {:ok, post} <-
+           post
+           |> Post.publish_changeset(published_at)
+           |> Core.Repo.update() do
+      Core.Syndication.syndicate_to_mastodon(post)
+
+      {:ok, post}
+    end
   end
 
   def delete_post(%Schema.Post{} = post, deleted_at \\ DateTime.utc_now()) do
diff --git a/lib/core/posts/post.ex b/lib/core/posts/post.ex
index 45cb7e1..cab8544 100644
--- a/lib/core/posts/post.ex
+++ b/lib/core/posts/post.ex
@@ -6,7 +6,16 @@ defmodule Core.Posts.Post do
   def content_changeset(%Schema.Post{} = post, attrs) do
     changeset =
       post
-      |> cast(attrs, [:tid, :kind, :slug, :title, :body, :deleted_at, :published_at])
+      |> cast(attrs, [
+        :tid,
+        :kind,
+        :slug,
+        :title,
+        :body,
+        :deleted_at,
+        :published_at,
+        :syndicate_to_mastodon
+      ])
       |> validate_required([:kind], message: "must have a kind")
       |> validate_required([:body], message: "must have a body")
 
@@ -77,7 +86,11 @@ defmodule Core.Posts.Post do
     import Ecto.Query
 
     def base do
-      from _ in Schema.Post, as: :posts
+      from p in Schema.Post,
+        as: :posts,
+        join: mp in assoc(p, :mastodon_post),
+        as: :mastodon_posts,
+        preload: [mastodon_post: mp]
     end
 
     def current(query \\ base()) do
diff --git a/lib/core/syndication.ex b/lib/core/syndication.ex
index fcf06d4..02bc49d 100644
--- a/lib/core/syndication.ex
+++ b/lib/core/syndication.ex
@@ -1,6 +1,11 @@
 defmodule Core.Syndication do
   alias __MODULE__
 
+  @mastodon_instance Application.compile_env!(:sloanely_but_surely, [
+                       Core.Syndication,
+                       :mastodon_instance
+                     ])
+
   def get_mastodon_account(user) do
     Core.Repo.get_by(Schema.MastodonAccount, user_id: user.id)
   end
@@ -11,4 +16,38 @@ defmodule Core.Syndication do
     |> Syndication.MastodonAccount.changeset(attrs)
     |> Core.Repo.insert()
   end
+
+  def syndicate_to_mastodon(%Schema.Post{} = post) do
+    post = Core.Repo.preload(post, [:mastodon_post])
+
+    if post.syndicate_to_mastodon and is_nil(post.mastodon_post) do
+      conn = build_mastodon_client_conn()
+
+      {:ok, resp} = MastodonClient.post(conn, "/api/v1/statuses", %{status: post.body})
+
+      post
+      |> Ecto.build_assoc(:mastodon_post)
+      |> Syndication.MastodonPost.changeset(%{
+        status_id: resp.body["id"],
+        url: resp.body["url"]
+      })
+      |> Core.Repo.insert()
+
+      post
+    else
+      post
+    end
+  end
+
+  defp get_mastodon_access_token do
+    mastodon_account = Core.Repo.one!(Schema.MastodonAccount)
+    mastodon_account.access_token
+  end
+
+  defp build_mastodon_client_conn do
+    %MastodonClient.Conn{
+      instance: @mastodon_instance,
+      access_token: get_mastodon_access_token()
+    }
+  end
 end
diff --git a/lib/core/syndication/mastodon_post.ex b/lib/core/syndication/mastodon_post.ex
new file mode 100644
index 0000000..5ee71b3
--- /dev/null
+++ b/lib/core/syndication/mastodon_post.ex
@@ -0,0 +1,10 @@
+defmodule Core.Syndication.MastodonPost do
+  import Ecto.Changeset
+
+  def changeset(mastodon_post, attrs) do
+    mastodon_post
+    |> cast(attrs, [:status_id, :url])
+    |> validate_required([:status_id, :url])
+    |> unique_constraint([:post_id])
+  end
+end
diff --git a/lib/schema/mastodon_post.ex b/lib/schema/mastodon_post.ex
new file mode 100644
index 0000000..12acc3d
--- /dev/null
+++ b/lib/schema/mastodon_post.ex
@@ -0,0 +1,11 @@
+defmodule Schema.MastodonPost do
+  use Schema
+
+  schema "mastodon_posts" do
+    field :status_id, :string
+    field :url, :string
+
+    belongs_to :post, Schema.Post
+    timestamps()
+  end
+end
diff --git a/lib/schema/post.ex b/lib/schema/post.ex
index 94ed07f..e913fb4 100644
--- a/lib/schema/post.ex
+++ b/lib/schema/post.ex
@@ -28,6 +28,10 @@ defmodule Schema.Post do
     field :published_at, :utc_datetime_usec
     field :deleted_at, :utc_datetime_usec
 
+    field :syndicate_to_mastodon, :boolean
+
+    has_one :mastodon_post, Schema.MastodonPost
+
     timestamps()
   end
 
diff --git a/lib/web/components/core_components.ex b/lib/web/components/core_components.ex
index 43cf78e..437777e 100644
--- a/lib/web/components/core_components.ex
+++ b/lib/web/components/core_components.ex
@@ -11,7 +11,7 @@ defmodule Web.CoreComponents do
   attr :label, :string, default: nil
   attr :value, :any
   attr :class, :string, default: nil
-  attr :type, :string, default: "text", values: ~w[text password textarea datetime-local]
+  attr :type, :string, default: "text", values: ~w[text password textarea datetime-local checkbox]
   attr :field, FormField
   attr :errors, :list, default: []
   attr :rest, :global, include: ~w[disabled form pattern placeholder readonly required]
@@ -35,6 +35,23 @@ defmodule Web.CoreComponents do
     |> input()
   end
 
+  def input(%{type: "checkbox"} = assigns) do
+    ~H"""
+    <div class={["flex flex-row items-center gap-x-2", @class]}>
+      <.label for={@id}>{@label}</.label>
+      <input type="hidden" name={@name} value="false" disabled={@rest[:disabled]} />
+      <input
+        id={@id}
+        type={@type}
+        name={@name}
+        value="true"
+        checked={Phoenix.HTML.Form.normalize_value(@type, @value)}
+        {@rest}
+      />
+    </div>
+    """
+  end
+
   def input(%{type: "textarea"} = assigns) do
     ~H"""
     <div class={["flex flex-col", @class]}>
diff --git a/lib/web/controllers/status_html/show.html.heex b/lib/web/controllers/status_html/show.html.heex
index bcd4982..bb4743d 100644
--- a/lib/web/controllers/status_html/show.html.heex
+++ b/lib/web/controllers/status_html/show.html.heex
@@ -44,14 +44,23 @@
       <.markdown content={@status.body} />
     </div>
 
-    <footer class="mt-4 border-t border-gray-200 pt-2 text-sm text-gray-500">
-      <%= if @status.published_at do %>
-        <.timex
-          value={Core.Posts.publish_date_time(@status)}
-          format="{Mfull} {D}, {YYYY} at {h12}:{m} {AM}"
-          class="dt-published"
-        />
-      <% end %>
+    <footer class="mt-4 border-t border-gray-200 pt-2 text-sm text-gray-500 flex flex-row justify-between">
+      <div>
+        <%= if @status.published_at do %>
+          <.timex
+            value={Core.Posts.publish_date_time(@status)}
+            format="{Mfull} {D}, {YYYY} at {h12}:{m} {AM}"
+            class="dt-published"
+          />
+        <% end %>
+      </div>
+      <div class="flex flex-row gap-x-2">
+        <%= if @status.mastodon_post do %>
+          <.link href={@status.mastodon_post.url} target="_blank" class="u-syndication">
+            tech.lgbt
+          </.link>
+        <% end %>
+      </div>
     </footer>
   </article>
 </div>
diff --git a/lib/web/live/admin_post_live.html.heex b/lib/web/live/admin_post_live.html.heex
index b8144bc..92408bf 100644
--- a/lib/web/live/admin_post_live.html.heex
+++ b/lib/web/live/admin_post_live.html.heex
@@ -25,6 +25,12 @@
       <.button phx-click="publish" class="self-end">publish</.button>
     <% end %>
     <.input type="datetime-local" label="deleted (utc)" field={@form[:deleted_at]} />
+    <.input
+      type="checkbox"
+      label="syndicate to mastodon"
+      class="self-end"
+      field={@form[:syndicate_to_mastodon]}
+    />
     <%= if @post.deleted_at do %>
       <.button phx-click="undelete" class="self-end">undelete</.button>
     <% else %>
diff --git a/priv/repo/migrations/20250429165843_add_mastodon_posts_table.exs b/priv/repo/migrations/20250429165843_add_mastodon_posts_table.exs
new file mode 100644
index 0000000..c0df6d3
--- /dev/null
+++ b/priv/repo/migrations/20250429165843_add_mastodon_posts_table.exs
@@ -0,0 +1,20 @@
+defmodule Core.Repo.Migrations.AddMastodonPostsTable do
+  use Ecto.Migration
+
+  def change do
+    create table(:mastodon_posts, primary_key: false) do
+      add :id, :uuid, primary_key: true
+      add :post_id, references(:posts, type: :uuid, on_delete: :delete_all), null: false
+      add :status_id, :text, null: false
+      add :url, :text, null: false
+
+      timestamps(type: :utc_datetime_usec)
+    end
+
+    create unique_index(:mastodon_posts, [:post_id])
+
+    alter table(:posts) do
+      add :syndicate_to_mastodon, :boolean, default: false, null: false
+    end
+  end
+end