diff --git a/README.md b/README.md
index f90f06b9d4aa1df58f631054f370a078b223932c..eaf89040a1ef0a0521b8bc8a104befe8fa74f610 100644
--- a/README.md
+++ b/README.md
@@ -62,5 +62,6 @@ Third party libraries and resources:
 * [github/gemoji](https://github.com/github/gemoji) (MIT) is used for emoji support (specifically the [emoji.json](https://raw.githubusercontent.com/github/gemoji/master/db/emoji.json) file)
 * [Lightbox with vanilla JS](https://yossiabramov.com/blog/vanilla-js-lightbox) as a lightbox on the landing page 
 * [HTTP middleware for gzip compression](https://gist.github.com/CJEnright/bc2d8b8dc0c1389a9feeddb110f822d7) (MIT) is used for serving static files
+* [Regex for auto-linking](https://github.com/bryanwoods/autolink-js) (MIT) is used to highlight links (the library is not used)
 * [Statically linking go-sqlite3](https://www.arp242.net/static-go.html)
 * [Linked tabs in mkdocs](https://facelessuser.github.io/pymdown-extensions/extensions/tabbed/#linked-tabs)
diff --git a/web/src/components/App.js b/web/src/components/App.js
index 21a0aa10fd389678932293504ff7814433af8bba..872681fce7ea1a8010283af51dc27e572716a57d 100644
--- a/web/src/components/App.js
+++ b/web/src/components/App.js
@@ -20,9 +20,6 @@ import ErrorBoundary from "./ErrorBoundary";
 import routes from "./routes";
 import {useAutoSubscribe, useConnectionListeners} from "./hooks";
 
-// TODO link lighlighting
-// TODO "copy url" toast
-// TODO "copy link url" button
 // TODO add drag and drop
 // TODO races when two tabs are open
 // TODO investigate service workers
diff --git a/web/src/components/Notifications.js b/web/src/components/Notifications.js
index 652a40c55ffdb26406061b17b0f5238d6b773192..38dc683b04c51208568240e5aaa2a3ac143ad3e8 100644
--- a/web/src/components/Notifications.js
+++ b/web/src/components/Notifications.js
@@ -1,5 +1,16 @@
 import Container from "@mui/material/Container";
-import {ButtonBase, CardActions, CardContent, CircularProgress, Fade, Link, Modal, Stack} from "@mui/material";
+import {
+    ButtonBase,
+    CardActions,
+    CardContent,
+    CircularProgress,
+    Fade,
+    Link,
+    Modal,
+    Snackbar,
+    Stack,
+    Tooltip
+} from "@mui/material";
 import Card from "@mui/material/Card";
 import Typography from "@mui/material/Typography";
 import * as React from "react";
@@ -9,7 +20,7 @@ import {
     formatMessage,
     formatShortDateTime,
     formatTitle,
-    openUrl,
+    openUrl, shortUrl,
     topicShortUrl,
     unmatchedTags
 } from "../app/utils";
@@ -66,6 +77,7 @@ const SingleSubscription = (props) => {
 const NotificationList = (props) => {
     const pageSize = 20;
     const notifications = props.notifications;
+    const [snackOpen, setSnackOpen] = useState(false);
     const [maxCount, setMaxCount] = useState(pageSize);
     const count = Math.min(notifications.length, maxCount);
 
@@ -81,7 +93,7 @@ const NotificationList = (props) => {
             dataLength={count}
             next={() => setMaxCount(prev => prev + pageSize)}
             hasMore={count < notifications.length}
-            loader={<h1>aa</h1>}
+            loader={<>Loading ...</>}
             scrollThreshold={0.7}
             scrollableTarget="main"
         >
@@ -91,7 +103,14 @@ const NotificationList = (props) => {
                         <NotificationItem
                             key={notification.id}
                             notification={notification}
+                            onShowSnack={() => setSnackOpen(true)}
                         />)}
+                    <Snackbar
+                        open={snackOpen}
+                        autoHideDuration={3000}
+                        onClose={() => setSnackOpen(false)}
+                        message="Copied to clipboard"
+                    />
                 </Stack>
             </Container>
         </InfiniteScroll>
@@ -109,6 +128,10 @@ const NotificationItem = (props) => {
         console.log(`[Notifications] Deleting notification ${notification.id} from ${subscriptionId}`);
         await subscriptionManager.deleteNotification(notification.id)
     }
+    const handleCopy = (s) => {
+        navigator.clipboard.writeText(s);
+        props.onShowSnack();
+    };
     const expired = attachment && attachment.expires && attachment.expires < Date.now()/1000;
     const showAttachmentActions = attachment && !expired;
     const showClickAction = notification.click;
@@ -133,22 +156,48 @@ const NotificationItem = (props) => {
                         </svg>}
                 </Typography>
                 {notification.title && <Typography variant="h5" component="div">{formatTitle(notification)}</Typography>}
-                <Typography variant="body1" sx={{ whiteSpace: 'pre-line' }}>{formatMessage(notification)}</Typography>
+                <Typography variant="body1" sx={{ whiteSpace: 'pre-line' }}>{autolink(formatMessage(notification))}</Typography>
                 {attachment && <Attachment attachment={attachment}/>}
                 {tags && <Typography sx={{ fontSize: 14 }} color="text.secondary">Tags: {tags}</Typography>}
             </CardContent>
             {showActions &&
                 <CardActions sx={{paddingTop: 0}}>
                     {showAttachmentActions && <>
-                        <Button onClick={() => navigator.clipboard.writeText(attachment.url)}>Copy URL</Button>
-                        <Button onClick={() => openUrl(attachment.url)}>Open attachment</Button>
+                        <Tooltip title="Copy attachment URL to clipboard">
+                            <Button onClick={() => handleCopy(attachment.url)}>Copy URL</Button>
+                        </Tooltip>
+                        <Tooltip title={`Go to ${attachment.url}`}>
+                            <Button onClick={() => openUrl(attachment.url)}>Open attachment</Button>
+                        </Tooltip>
+                    </>}
+                    {showClickAction && <>
+                        <Tooltip title="Copy link URL to clipboard">
+                            <Button onClick={() => handleCopy(notification.click)}>Copy link</Button>
+                        </Tooltip>
+                        <Tooltip title={`Go to ${notification.click}`}>
+                            <Button onClick={() => openUrl(notification.click)}>Open link</Button>
+                        </Tooltip>
                     </>}
-                    {showClickAction && <Button onClick={() => openUrl(notification.click)}>Open link</Button>}
                 </CardActions>}
         </Card>
     );
 }
 
+/**
+ * Replace links with <Link/> components; this is a combination of the genius function
+ * in [1] and the regex in [2].
+ *
+ * [1] https://github.com/facebook/react/issues/3386#issuecomment-78605760
+ * [2] https://github.com/bryanwoods/autolink-js/blob/master/autolink.js#L9
+ */
+const autolink = (s) => {
+    const parts = s.split(/(\bhttps?:\/\/[\-A-Z0-9+\u0026\u2019@#\/%?=()~_|!:,.;]*[\-A-Z0-9+\u0026@#\/%=~()_|]\b)/gi);
+    for (let i = 1; i < parts.length; i += 2) {
+        parts[i] = <Link key={i} href={parts[i]} underline="hover" target="_blank" rel="noreferrer,noopener">{shortUrl(parts[i])}</Link>;
+    }
+    return <>{parts}</>;
+};
+
 const priorityFiles = {
     1: priority1,
     2: priority2,