Microsoft Lync and Skype for Business have a rich set of .NET APIs which make it easy to extend the platform and integrate it with other applications. This blog helps explain how to use those APIs.

ExtensionMethods.cs

using System.Threading.Tasks;
using Microsoft.Rtc.Collaboration;
using Microsoft.Rtc.Signaling;

namespace LyncBreakReminder
{
    public static class ExtensionMethods
    {
        public static Task StartupAsync(this CollaborationPlatform platform)
        {
            return Task.Factory.FromAsync(platform.BeginStartup, 
                platform.EndStartup, null);
        }

        public static Task ShutdownAsync
            (this CollaborationPlatform platform)
        {
            return Task.Factory.FromAsync(platform.BeginShutdown, 
                platform.EndShutdown, null);
        }

        public static Task<SipResponseData> EstablishAsync(this 
            LocalEndpoint endpoint)
        {
            return Task<SipResponseData>.Factory.FromAsync(
                endpoint.BeginEstablish, 
                endpoint.EndEstablish, null);
        }

        public static Task TerminateAsync(this LocalEndpoint endpoint)
        {
            return Task.Factory.FromAsync(endpoint.BeginTerminate, 
                endpoint.EndTerminate, null);
        }

        public static Task<CallMessageData> AcceptAsync(this Call call)
        {
            return Task<CallMessageData>.Factory.FromAsync(call.BeginAccept, 
                call.EndAccept, null);
        }

        public static Task<CallMessageData> EstablishAsync(this Call call, 
            string destinationUri, CallEstablishOptions options)
        {
            return Task<CallMessageData>.Factory.FromAsync(
                call.BeginEstablish, call.EndEstablish, 
                destinationUri, options, null);
        }

        public static Task TerminateAsync(this Call call)
        {
            return Task.Factory.FromAsync(call.BeginTerminate, 
                call.EndTerminate, null);
        }

        public static Task<SendInstantMessageResult> 
            SendInstantMessageAsync(this InstantMessagingFlow flow, 
            string textBody)
        {
            return Task<SendInstantMessageResult>.Factory.FromAsync(
                flow.BeginSendInstantMessage, 
                flow.EndSendInstantMessage, textBody, null);
        }
    }
}

BreakReminderUserAgent.cs

using System;
using System.Collections.Concurrent;
using System.Configuration;
using Microsoft.Rtc.Collaboration;
using Microsoft.Rtc.Signaling;

namespace LyncBreakReminder
{
    internal class BreakReminderUserAgent
    {
        CollaborationPlatform _platform;
        ApplicationEndpoint _endpoint;

        // Collection of break reminder sessions currently in use
        readonly ConcurrentDictionary<string, BreakReminderSession> _sessions = 
            new ConcurrentDictionary<string,BreakReminderSession>();

        internal async void Start()
        {
            string applicationId = 
                ConfigurationManager.AppSettings["applicationId"];

            ProvisionedApplicationPlatformSettings settings = 
                new ProvisionedApplicationPlatformSettings("breakreminder", 
                    applicationId);

            _platform = new CollaborationPlatform(settings);
            _platform.RegisterForApplicationEndpointSettings(
                OnApplicationEndpointDiscovered);

            try
            {
                await _platform.StartupAsync();
            }
            catch (InvalidOperationException iex)
            {
                Console.WriteLine(iex);
            }
            catch (RealTimeException rex)
            {
                Console.WriteLine(rex);
            }
        }

        internal async void Stop()
        {
            try
            {
                await _endpoint.TerminateAsync();
                await _platform.ShutdownAsync();
            }
            catch (InvalidOperationException iex)
            {
                Console.WriteLine(iex);
            }
            catch (RealTimeException rex)
            {
                Console.WriteLine(rex);
            }

            try
            {
                _endpoint.BeginTerminate(terminateAsyncResult =>
                {
                    try
                    {
                        _endpoint.EndTerminate(terminateAsyncResult);

                        try
                        {
                            _platform.BeginShutdown(shutdownAsyncResult =>
                                {
                                    try
                                    {
                                        _platform.EndShutdown(shutdownAsyncResult);
                                    }
                                    catch (RealTimeException ex)
                                    {
                                        Console.WriteLine(ex);
                                    }
                                },
                                null);
                        }
                        catch (InvalidOperationException ex)
                        {
                            Console.WriteLine(ex);
                        }
                    }
                    catch (RealTimeException ex)
                    {
                        Console.WriteLine(ex);
                    }
                },
                null);
            }
            catch (InvalidOperationException ex)
            {
                Console.WriteLine(ex);
            }
        }

        private async void OnApplicationEndpointDiscovered(object sender, 
            ApplicationEndpointSettingsDiscoveredEventArgs e)
        {
            _endpoint = new ApplicationEndpoint(_platform, 
                e.ApplicationEndpointSettings);

            try
            {
                await _endpoint.EstablishAsync();

                _endpoint.RegisterForIncomingCall<InstantMessagingCall>(
                    OnIncomingInstantMessagingCallReceived);
            }
            catch (InvalidOperationException iex)
            {
                Console.WriteLine(iex);
            }
            catch (RealTimeException rex)
            {
                Console.WriteLine(rex);
            }
        }

        private void OnIncomingInstantMessagingCallReceived(object sender, 
            CallReceivedEventArgs<InstantMessagingCall> e)
        {
            // Grab the SIP URI of the user sending the IM.
            string sipUri = e.Call.RemoteEndpoint.Participant.Uri;

            // Create a new session to use if there isn't already one,
            // and subscribe to session status changes in advance to avoid
            // race conditions.
            BreakReminderSession newSession = new BreakReminderSession(sipUri);
            newSession.BreakReminderSessionStatusChanged += 
                OnBreakReminderSessionStatusChanged;

            // Get the existing session or add the new one in a threadsafe way.
            BreakReminderSession sessionToUse = _sessions.GetOrAdd(sipUri, 
                newSession);

            // Pass the IM along to the session.
            sessionToUse.HandleMessage(e.Call, e.ToastMessage);

            // If we didn't end up using the new session, unsubscribe our
            // event handler to avoid memory leaks.
            if (newSession != sessionToUse)
            {
                newSession.BreakReminderSessionStatusChanged -= 
                    OnBreakReminderSessionStatusChanged;
            }
        }

        void OnBreakReminderSessionStatusChanged(object sender, 
            BreakReminderSessionStatusChangedEventArgs e)
        {
            if (e.Status == BreakReminderSessionStatus.Terminated)
            {
                // If a session becomes terminated, remove it from the dictionary
                // in a threadsafe manner and unsubscribe from events.
                BreakReminderSession session = (BreakReminderSession)sender;

                BreakReminderSession removedSession;
                bool succeeded = _sessions.TryRemove(session.SipUri, out 
                    removedSession);

                session.BreakReminderSessionStatusChanged -= 
                    OnBreakReminderSessionStatusChanged;
            }
        }
    }
}

BreakReminderSession.cs

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Rtc.Collaboration;
using Microsoft.Rtc.Collaboration.Presence;
using Microsoft.Rtc.Signaling;

namespace LyncBreakReminder
{
    internal class BreakReminderSession
    {
        internal event EventHandler<BreakReminderSessionStatusChangedEventArgs> 
            BreakReminderSessionStatusChanged;

        internal string SipUri { get; set; }
        internal BreakReminderSessionStatus Status
        {
            get
            {
                return _status;
            }
            set
            {
                lock (_statusSyncObject)
                {
                    BreakReminderSessionStatus oldStatus = _status;
                    _status = value;

                    if (_status != oldStatus)
                    {
                        if (BreakReminderSessionStatusChanged != null)
                        {
                            BreakReminderSessionStatusChanged(this, 
                                new BreakReminderSessionStatusChangedEventArgs()
                            {
                                PreviousStatus = oldStatus,
                                Status = _status
                            });
                        }
                    }
                }
            }
        }

        private BreakReminderSessionStatus _status = 
            BreakReminderSessionStatus.New;
        private LocalEndpoint _endpoint;
        private RemotePresenceView _remotePresenceView;
        private Timer _availableTimer;
        private bool _availableTimerActive;
        private int _minutesBetweenBreaks = 30;

        private readonly object _timerSyncObject = new object();
        private readonly object _statusSyncObject = new object();
        private readonly object _monitoringSyncObject = new object();

        internal BreakReminderSession(string startingSipUri)
        {
            SipUri = startingSipUri;

            try
            {
                _minutesBetweenBreaks =
                    int.Parse(
                    ConfigurationManager.AppSettings["minutesBetweenBreaks"]);
            }
            catch (ConfigurationErrorsException)
            {
                Console.WriteLine("No minutes between breaks setting found.");
            }
            catch (FormatException)
            {
                Console.WriteLine("Invalid minutes between breaks setting.");
            }
        }

        internal async void HandleMessage(InstantMessagingCall call, 
            ToastMessage toast)
        {
            _endpoint = call.Conversation.Endpoint;

            try
            {
                await call.AcceptAsync();

                if (this.Status == BreakReminderSessionStatus.New)
                {
                    await call.Flow.SendInstantMessageAsync(
                        "Starting break monitoring.");
                    await call.TerminateAsync();

                    StartBreakMonitoring();
                }
                else
                {
                    await call.Flow.SendInstantMessageAsync(
                        "Ending break monitoring.");
                    await call.TerminateAsync();

                    StopBreakMonitoring();
                }
            }
            catch (InvalidOperationException ex)
            {
                Console.WriteLine(ex);
            }
            catch (RealTimeException rex)
            {
                Console.WriteLine(rex);
            }
        }

        private void StartBreakMonitoring()
        {
            lock (_monitoringSyncObject)
            {
                this.Status = BreakReminderSessionStatus.Initiating;

                List<RemotePresentitySubscriptionTarget> targets = 
                    new List<RemotePresentitySubscriptionTarget>();
                targets.Add(new RemotePresentitySubscriptionTarget(this.SipUri));

                _remotePresenceView = new RemotePresenceView(_endpoint);
                _remotePresenceView.PresenceNotificationReceived += 
                    OnPresenceNotificationReceived;
                _remotePresenceView.StartSubscribingToPresentities(targets);

                this.Status = BreakReminderSessionStatus.Active;
            }
        }

        void OnPresenceNotificationReceived(object sender, 
            RemotePresentitiesNotificationEventArgs e)
        {
            foreach (var notification in e.Notifications)
            {
                if (notification.AggregatedPresenceState.Availability == 
                    PresenceAvailability.Online)
                {
                    StartAvailableTimer();
                }
                else
                {
                    StopAvailableTimer();
                }
            }
        }

        private void StopBreakMonitoring()
        {
            lock (_monitoringSyncObject)
            {
                this.Status = BreakReminderSessionStatus.Terminating;

                StopAvailableTimer();

                List<string> targets = new List<string>();
                targets.Add(this.SipUri);

                _remotePresenceView.PresenceNotificationReceived -= 
                    OnPresenceNotificationReceived;
                _remotePresenceView.StartUnsubscribingToPresentities(targets);

                this.Status = BreakReminderSessionStatus.Terminated;
            }
        }

        private void StartAvailableTimer()
        {
            lock (_timerSyncObject)
            {
                if (_availableTimerActive)
                {
                    Console.WriteLine(
                        "Did not start available timer -- already active.");
                }
                else
                {
                    _availableTimerActive = true;
                    _availableTimer = new Timer(OnAvailableTimerElapsed, null, 
                        TimeSpan.FromMinutes(_minutesBetweenBreaks), 
                        TimeSpan.FromMinutes(_minutesBetweenBreaks));
                }
            }
        }

        private async void OnAvailableTimerElapsed(object state)
        {
            try
            {
                Conversation reminderConversation = new Conversation(_endpoint);
                InstantMessagingCall reminderCall = 
                    new InstantMessagingCall(reminderConversation);

                await reminderCall.EstablishAsync(this.SipUri, null);
                await reminderCall.Flow.SendInstantMessageAsync(
                    "This is your reminder to take a break.");
                await reminderCall.TerminateAsync();
            }
            catch (InvalidOperationException ex)
            {
                Console.WriteLine(ex);
            }
            catch (RealTimeException rex)
            {
                Console.WriteLine(rex);
            }
        }

        private void StopAvailableTimer()
        {
            lock (_timerSyncObject)
            {
                if (!_availableTimerActive)
                {
                    Console.WriteLine(
                        "Did not stop available timer -- not started.");
                }
                else
                {
                    _availableTimer.Dispose();
                    _availableTimer = null;
                    _availableTimerActive = false;
                }
            }
        }
    }

    internal enum BreakReminderSessionStatus
    {
        New,
        Initiating,
        Active,
        Terminating,
        Terminated
    }

    internal class BreakReminderSessionStatusChangedEventArgs : EventArgs
    {
        internal BreakReminderSessionStatus PreviousStatus { get; set; }
        internal BreakReminderSessionStatus Status { get; set; }
    }
}

Program.cs

using System;

namespace LyncBreakReminder
{
    class Program
    {
        static void Main(string[] args)
        {
            BreakReminderUserAgent ua = new BreakReminderUserAgent();
            ua.Start();

            Console.WriteLine("started");

            Console.ReadLine();

            ua.Stop();

            Console.WriteLine("stopping");

            Console.ReadLine();
        }
    }
}

App.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
  <appSettings>
    <add key="applicationId" value="urn:application:mgtest"/>
    <add key="minutesBetweenBreaks" value="1"/>
  </appSettings>
</configuration>