diff --git a/packages/core/src/analytics/__tests__/dispatch.test.ts b/packages/core/src/analytics/__tests__/dispatch.test.ts
new file mode 100644
index 000000000..18efbb70f
--- /dev/null
+++ b/packages/core/src/analytics/__tests__/dispatch.test.ts
@@ -0,0 +1,103 @@
+// import { CoreContext } from '../context'
+// import { CoreSegmentEvent, Callback } from '../events/interfaces'
+
+const isOnline = jest.fn().mockReturnValue(true)
+const isOffline = jest.fn().mockReturnValue(false)
+
+jest.mock('../../connection', () => ({
+  isOnline,
+  isOffline,
+}))
+const fetcher = jest.fn()
+jest.mock('node-fetch', () => fetcher)
+
+const invokeCallback = jest.fn()
+
+jest.mock('../../callback', () => ({
+  invokeCallback: invokeCallback,
+}))
+
+import { EventQueue } from '../../queue/event-queue'
+import { Emitter } from '../../emitter'
+import { dispatch, getDelayTimeout } from '../dispatch'
+import { PriorityQueue } from '../../priority-queue'
+
+let emitter!: Emitter
+let queue!: EventQueue
+const dispatchSingleSpy = jest.spyOn(EventQueue.prototype, 'dispatchSingle')
+const dispatchSpy = jest.spyOn(EventQueue.prototype, 'dispatch')
+
+beforeEach(() => {
+  queue = new EventQueue(new PriorityQueue(4, []))
+  emitter = new Emitter()
+})
+
+afterEach(() => {
+  jest.resetAllMocks()
+})
+
+describe('Dispatch', () => {
+  it('should return ctx if offline and retryQueue is false', async () => {
+    isOnline.mockReturnValue(false)
+    isOffline.mockReturnValue(true)
+
+    const ctx = await dispatch({ type: 'screen' }, queue, emitter, {
+      retryQueue: false,
+    })
+    expect(ctx.event.type).toBe('screen')
+    const called = Boolean(
+      dispatchSingleSpy.mock.calls.length || dispatchSpy.mock.calls.length
+    )
+    expect(called).toBeFalsy()
+  })
+
+  it('should not dispatch if offline and retryQueue is true', async () => {
+    isOnline.mockReturnValue(false)
+    isOffline.mockReturnValue(true)
+
+    await dispatch({ type: 'screen' }, queue, emitter, {
+      retryQueue: true,
+    })
+    const called = Boolean(
+      dispatchSingleSpy.mock.calls.length || dispatchSpy.mock.calls.length
+    )
+    expect(called).toBeTruthy()
+  })
+
+  it('should call dispatchSingle with ctx if queue is empty', async () => {
+    queue.isEmpty = jest.fn().mockReturnValue(true)
+    await dispatch({ type: 'screen' }, queue, emitter)
+    expect(dispatchSingleSpy).toBeCalledWith(
+      expect.objectContaining({ event: { type: 'screen' } })
+    )
+    expect(dispatchSpy).not.toBeCalled()
+  })
+  it('should call dispatch with ctx if queue is empty', async () => {
+    queue.isEmpty = jest.fn().mockReturnValue(false)
+    await dispatch({ type: 'screen' }, queue, emitter)
+    expect(dispatchSpy).toBeCalledWith(
+      expect.objectContaining({ event: { type: 'screen' } })
+    )
+    expect(dispatchSingleSpy).not.toBeCalled()
+  })
+  it('should not invoke callback if no callback is passed', async () => {
+    await dispatch({ type: 'screen' }, queue, emitter)
+    expect(invokeCallback).not.toBeCalled()
+  })
+  it('should call "invokeCallback" if callback is passed', async () => {
+    const cb = jest.fn()
+    await dispatch({ type: 'screen' }, queue, emitter, { callback: cb })
+    expect(invokeCallback).toBeCalled()
+  })
+})
+
+describe('getDelayTimeout', () => {
+  it('should work as expected', () => {
+    const aShortTimeAgo = Date.now() - 200
+    expect(Math.round(getDelayTimeout(aShortTimeAgo, 500))).toBe(300)
+  })
+  it('should have a sensible default', () => {
+    const aShortTimeAgo = Date.now() - 200
+    expect(Math.round(getDelayTimeout(aShortTimeAgo))).toBe(100)
+  })
+})
diff --git a/packages/core/src/analytics/dispatch.ts b/packages/core/src/analytics/dispatch.ts
index 3645d02af..ccde20b45 100644
--- a/packages/core/src/analytics/dispatch.ts
+++ b/packages/core/src/analytics/dispatch.ts
@@ -12,6 +12,14 @@ type DispatchOptions = {
   retryQueue?: boolean
 }
 
+/* The amount of time in ms to wait before invoking the callback. */
+export const getDelayTimeout = (
+  startTimeInEpochMS: number,
+  timeoutInMS?: number
+) => {
+  const elapsedTime = Date.now() - startTimeInEpochMS
+  return Math.max((timeoutInMS ?? 300) - elapsedTime, 0)
+}
 /**
  * Push an event into the dispatch queue and invoke any callbacks.
  *
@@ -40,14 +48,13 @@ export async function dispatch(
   } else {
     dispatched = await queue.dispatch(ctx)
   }
-  const elapsedTime = Date.now() - startTime
   const timeoutInMs = options?.timeout
 
   if (options?.callback) {
     dispatched = await invokeCallback(
       dispatched,
       options.callback,
-      Math.max((timeoutInMs ?? 300) - elapsedTime, 0),
+      getDelayTimeout(startTime, timeoutInMs),
       timeoutInMs
     )
   }