diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js
index d6d9302fde..a07d11626d 100644
--- a/src/components/views/context_menus/MessageContextMenu.js
+++ b/src/components/views/context_menus/MessageContextMenu.js
@@ -43,26 +43,40 @@ module.exports = React.createClass({
     getInitialState: function() {
         return {
             canRedact: false,
+            canPin: false,
         };
     },
 
     componentWillMount: function() {
-        MatrixClientPeg.get().on('RoomMember.powerLevel', this._checkCanRedact);
-        this._checkCanRedact();
+        MatrixClientPeg.get().on('RoomMember.powerLevel', this._checkPermissions);
+        this._checkPermissions();
     },
 
     componentWillUnmount: function() {
         const cli = MatrixClientPeg.get();
         if (cli) {
-            cli.removeListener('RoomMember.powerLevel', this._checkCanRedact);
+            cli.removeListener('RoomMember.powerLevel', this._checkPermissions);
         }
     },
 
-    _checkCanRedact: function() {
+    _checkPermissions: function() {
         const cli = MatrixClientPeg.get();
         const room = cli.getRoom(this.props.mxEvent.getRoomId());
+
         const canRedact = room.currentState.maySendRedactionForEvent(this.props.mxEvent, cli.credentials.userId);
-        this.setState({canRedact});
+        let canPin = room.currentState.mayClientSendStateEvent('m.room.pinned_events', cli);
+
+        // HACK: Intentionally say we can't pin if the user doesn't want to use the functionality
+        if (!UserSettingsStore.isFeatureEnabled("feature_pinning")) canPin = false;
+
+        this.setState({canRedact, canPin});
+    },
+
+    _isPinned: function() {
+        const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
+        const pinnedEvent = room.currentState.getStateEvents('m.room.pinned_events', '');
+        if (!pinnedEvent) return false;
+        return pinnedEvent.getContent().pinned.includes(this.props.mxEvent.getId());
     },
 
     onResendClick: function() {
@@ -122,6 +136,28 @@ module.exports = React.createClass({
         this.closeMenu();
     },
 
+    onPinClick: function() {
+        MatrixClientPeg.get().getStateEvent(this.props.mxEvent.getRoomId(), 'm.room.pinned_events', '')
+            .catch(e => {
+                // Intercept the Event Not Found error and fall through the promise chain with no event.
+                if (e.errcode === "M_NOT_FOUND") return null;
+                throw e;
+            })
+            .then(event => {
+                const eventIds = (event ? event.pinned : []) || [];
+                if (!eventIds.includes(this.props.mxEvent.getId())) {
+                    // Not pinned - add
+                    eventIds.push(this.props.mxEvent.getId());
+                } else {
+                    // Pinned - remove
+                    eventIds.splice(eventIds.indexOf(this.props.mxEvent.getId()), 1);
+                }
+
+                MatrixClientPeg.get().sendStateEvent(this.props.mxEvent.getRoomId(), 'm.room.pinned_events', {pinned: eventIds}, '');
+            });
+        this.closeMenu();
+    },
+
     closeMenu: function() {
         if (this.props.onFinished) this.props.onFinished();
     },
@@ -147,6 +183,7 @@ module.exports = React.createClass({
         let redactButton;
         let cancelButton;
         let forwardButton;
+        let pinButton;
         let viewSourceButton;
         let viewClearSourceButton;
         let unhidePreviewButton;
@@ -186,6 +223,14 @@ module.exports = React.createClass({
                         { _t('Forward Message') }
                     </div>
                 );
+
+                if (this.state.canPin) {
+                    pinButton = (
+                        <div className="mx_MessageContextMenu_field" onClick={this.onPinClick}>
+                            {this._isPinned() ? _t('Unpin Message') : _t('Pin Message')}
+                        </div>
+                    );
+                }
             }
         }
 
@@ -246,6 +291,7 @@ module.exports = React.createClass({
                 {redactButton}
                 {cancelButton}
                 {forwardButton}
+                {pinButton}
                 {viewSourceButton}
                 {viewClearSourceButton}
                 {unhidePreviewButton}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 74e59f5fef..16d47d1020 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -200,6 +200,11 @@
   "You have successfully set a password!": "You have successfully set a password!",
   "You can now return to your account after signing out, and sign in on other devices.": "You can now return to your account after signing out, and sign in on other devices.",
   "Continue": "Continue",
+  "Pin Message": "Pin Message",
+  "Unpin Message": "Unpin Message",
+  "Jump to message": "Jump to message",
+  "No pinned messages.": "No pinned messages.",
+  "Loading...": "Loading...",
   "Please set a password!": "Please set a password!",
   "This will allow you to return to your account after signing out, and sign in on other devices.": "This will allow you to return to your account after signing out, and sign in on other devices.",
   "You have successfully set a password and an email address!": "You have successfully set a password and an email address!",
diff --git a/src/i18n/strings/en_US.json b/src/i18n/strings/en_US.json
index 8d7eb15d54..ef3eff3503 100644
--- a/src/i18n/strings/en_US.json
+++ b/src/i18n/strings/en_US.json
@@ -185,6 +185,11 @@
     "You have successfully set a password and an email address!": "You have successfully set a password and an email address!",
     "Remember, you can always set an email address in user settings if you change your mind.": "Remember, you can always set an email address in user settings if you change your mind.",
     "Warning": "Warning",
+    "Pin Message": "Pin Message",
+    "Unpin Message": "Unpin Message",
+    "Jump to message": "Jump to message",
+    "No pinned messages.": "No pinned messages.",
+    "Loading...": "Loading...",
     "Checking for an update...": "Checking for an update...",
     "Error encountered (%(errorDetail)s).": "Error encountered (%(errorDetail)s).",
     "No update available.": "No update available.",
diff --git a/src/skins/vector/css/_components.scss b/src/skins/vector/css/_components.scss
index 4948ac8520..82c2c18641 100644
--- a/src/skins/vector/css/_components.scss
+++ b/src/skins/vector/css/_components.scss
@@ -68,6 +68,8 @@
 @import "./matrix-react-sdk/views/voip/_CallView.scss";
 @import "./matrix-react-sdk/views/voip/_IncomingCallbox.scss";
 @import "./matrix-react-sdk/views/voip/_VideoView.scss";
+@import "./matrix-react-sdk/views/rooms/_PinnedEventsPanel.scss";
+@import "./matrix-react-sdk/views/rooms/_PinnedEventTile.scss";
 @import "./vector-web/_fonts.scss";
 @import "./vector-web/structures/_CompatibilityPage.scss";
 @import "./vector-web/structures/_HomePage.scss";
diff --git a/src/skins/vector/css/matrix-react-sdk/views/rooms/_PinnedEventTile.scss b/src/skins/vector/css/matrix-react-sdk/views/rooms/_PinnedEventTile.scss
new file mode 100644
index 0000000000..ca790ef8f0
--- /dev/null
+++ b/src/skins/vector/css/matrix-react-sdk/views/rooms/_PinnedEventTile.scss
@@ -0,0 +1,67 @@
+/*
+Copyright 2017 Travis Ralston
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+.mx_PinnedEventTile {
+    min-height: 40px;
+    margin-bottom: 5px;
+    width: 100%;
+    border-radius: 5px; // for the hover
+}
+
+.mx_PinnedEventTile:hover {
+    background-color: $event-selected-color;
+}
+
+.mx_PinnedEventTile .mx_PinnedEventTile_sender {
+    color: #868686;
+    font-size: 0.8em;
+    vertical-align: top;
+    display: block;
+    padding-bottom: 3px;
+}
+
+.mx_PinnedEventTile .mx_EventTile_content {
+    margin-left: 50px;
+    position: relative;
+    top: 0;
+    left: 0;
+}
+
+.mx_PinnedEventTile .mx_BaseAvatar {
+    float: left;
+    margin-right: 10px;
+}
+
+.mx_PinnedEventTile:hover .mx_PinnedEventTile_actions {
+    display: block;
+}
+
+.mx_PinnedEventTile_actions {
+    float: right;
+    margin-right: 10px;
+    display: none;
+}
+
+.mx_PinnedEventTile_unpinButton {
+    display: inline-block;
+    cursor: pointer;
+    margin-left: 10px;
+}
+
+.mx_PinnedEventTile_gotoButton {
+    display: inline-block;
+    font-size: 0.8em;
+}
diff --git a/src/skins/vector/css/matrix-react-sdk/views/rooms/_PinnedEventsPanel.scss b/src/skins/vector/css/matrix-react-sdk/views/rooms/_PinnedEventsPanel.scss
new file mode 100644
index 0000000000..663d5bdf6e
--- /dev/null
+++ b/src/skins/vector/css/matrix-react-sdk/views/rooms/_PinnedEventsPanel.scss
@@ -0,0 +1,37 @@
+/*
+Copyright 2017 Travis Ralston
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+.mx_PinnedEventsPanel {
+    border-top: 1px solid $primary-hairline-color;
+}
+
+.mx_PinnedEventsPanel_body {
+    max-height: 300px;
+    overflow-y: auto;
+    padding-bottom: 15px;
+}
+
+.mx_PinnedEventsPanel_header {
+    margin: 0;
+    padding-top: 8px;
+    padding-bottom: 15px;
+}
+
+.mx_PinnedEventsPanel_cancel {
+    margin: 12px;
+    float: right;
+    display: inline-block;
+}
diff --git a/src/skins/vector/img/icons-pin.svg b/src/skins/vector/img/icons-pin.svg
new file mode 100644
index 0000000000..a6fbf13baa
--- /dev/null
+++ b/src/skins/vector/img/icons-pin.svg
@@ -0,0 +1,7 @@
+<svg width="16px" height="16px" viewbox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
+ <g transform="scale(0.03125)">
+  <path id="svg_2" fill="none" stroke="#76cfa6" stroke-width="40" stroke-linecap="round" stroke-linejoin="round" d="m315.802,402.338c12.73,-33.537 13.503,-69.629 3.623,-102.697l93.245,-103.107l7.831,7.831c10.411,10.409 27.283,10.409 37.691,0c10.41,-10.408 10.41,-27.281 0.001,-37.69l-112.869,-112.867c-10.407,-10.409 -27.279,-10.41 -37.689,-0.001c-10.408,10.41 -10.409,27.283 0.001,37.693l7.833,7.833l-103.107,93.243c-33.069,-9.878 -69.163,-9.107 -102.697,3.626c-4.7,1.785 -8.001,5.646 -9.059,10.604c-1.175,5.473 0.627,11.402 4.697,15.472l184.42,184.421c4.069,4.07 10,5.871 15.472,4.695c4.959,-1.055 8.82,-4.357 10.607,-9.056z"/>
+  <polyline id="svg_3" fill="none" stroke="#76cfa6" stroke-width="40" stroke-linecap="round" stroke-linejoin="round" points="    180.951,297.927 46,466 215.319,332.295   "/>
+  <!--<line id="svg_4" fill="none" stroke="#76cfa6" stroke-width="40" stroke-linecap="round" stroke-linejoin="round" y2="219.549" y1="138.166" x2="255.531" x1="336.915"/>-->
+ </g>
+</svg>
\ No newline at end of file