Tymeslot
tymeslot.appOpen-source (AGPL-3.0), self-hostable scheduling and calendar platform. Guests book without creating an account; hosts get a full calendar with two-way sync to Google, Outlook and iCloud. A managed cloud plan is offered too, not self-hosted.
- Homepage:tymeslot.app
- GitHub:github.com/Tymeslot/tymeslot
- Web info:web-check.xyz/check/tymeslot.app
Tymeslot Source Code
Author
Description
Open-source meeting scheduling platform built with Elixir/Phoenix LiveView. Self-host or use the managed cloud at tymeslot.app.
Homepage
https://tymeslot.appRepository
- LicenseAGPL-3.0
- Created07 Aug 25
- Primary languageElixir
- Size182,059 KB
- Stars186
- Forks28
- Watchers186
Top Contributors
@wahve3 (3604)
@ToxicToxster (5)
@evoludigit (3)
@PixelJonas (1)
@lostbean (1)
Recent Commits
Luka Breitig(02 Sept 26)
chore: bump version to 1.14.0 Signed-off-by: Luka Breitig <[email protected]>
Luka Breitig(02 Sept 26)
chore(core): check the gettext templates are up to date in precommit An entire feature's user-facing copy reached a release candidate without ever being extracted. The completeness test could not catch it: it compares translations against the templates, not the templates against the source, so templates that were never regenerated look complete. Adding the extraction check to the gate closes that gap. It writes nothing, and it leaves the build warm for the steps behind it. Signed-off-by: Luka Breitig <[email protected]>
Luka Breitig(02 Sept 26)
chore(core): translate the Exchange connection form The templates had not been re-extracted since the Exchange integration landed, so its whole connection form, its mailbox validation messages and the certificate-rejected sentence would have reached every non-English user in English. Nineteen entries per locale, translated into German, French, Italian, Czech and Ukrainian using the terminology the calendar catalogues already established. Signed-off-by: Luka Breitig <[email protected]>
Luka Breitig(02 Sept 26)
chore(core): expose the read-only provider check on the calendar context Adds `read_only_provider?/1` and a public type for a block of busy time to the calendar context, so callers outside it stop reaching past the context into the provider configuration and the free/busy parser. Both are thin delegates over what those modules already provide, and a test pins them so a later rename inside the context fails here first. Signed-off-by: Luka Breitig <[email protected]>
Luka Breitig(02 Sept 26)
fix(core): say when an SMTP CA bundle is ignored or cannot be read Setting a CA bundle and then disabling certificate verification left the bundle validated at boot and then silently unused. An operator who turned verification off while getting a relay working, fixed the bundle path and never removed the override had verification off with nothing saying so. The boot warning now names the ignored bundle alongside what disabling verification gives up, in one message rather than two. The bundle check also tested only that the path was a regular file while reporting "not found or not readable", so a bundle with the wrong ownership after a volume mount passed the check and failed later inside the TLS handshake. It is now opened for reading, and the raise names the reason. Changelog: skip Signed-off-by: Luka Breitig <[email protected]>
Luka Breitig(02 Sept 26)
fix(core): anchor the booking interval grid to the host's clock A host who sets how far apart booking times are offered means it on their own calendar. The grid was aligned after the window had already been shifted into the visitor's timezone, so it landed on round numbers for whoever happened to be looking. For a visitor whose timezone is offset by half an hour, a host asking for hourly bookings received them at half past the hour, and the rounding, which only ever moves forward, discarded the first partial hour so that visitor was offered one slot fewer than the host had opened. Because the timezone arrives as a request parameter, the phase of the host's grid was decided by the visitor. Alignment now happens on the host's wall clock and the result is converted for display, so every visitor sees the same underlying grid and none of it is rounded away. Meeting types with no interval set are untouched: that path never aligns at all, and its output was compared against the previous release across 10,500 combinations of timezone, daylight-saving transition, duration, window shape and break set with no difference. Changelog: skip Signed-off-by: Luka Breitig <[email protected]>
Luka Breitig(02 Sept 26)
fix(core): recognise read-only calendars at every booking-target gate Whether a calendar can receive a booking was re-derived at each gate that picks one, and each asked whether the provider was a subscribed feed rather than whether it refuses writes. A mailbox refuses writes without being a feed, so it passed every gate but one. A user with a writable primary calendar, a second writable calendar and a mailbox who deleted the primary would have had the mailbox promoted ahead of the writable calendar, because promotion takes the most recently added eligible integration. From then on every booking would have failed to reach any calendar, retried five times and emailed the owner, while a perfectly good calendar sat unused beside it. The question now lives in one module that every gate calls, so a future read-only provider is one edit rather than nine. Feed-specific routing stays on the feed check: a mailbox has real folders to discover and no feed to re-fetch, so it must not be sent down the subscription refresh path. This also restores a warning the same work had silently killed. The card that tells a user their chosen booking calendar has turned read-only on the server was guarded on the shape of its argument rather than on the question it meant to ask, so it matched every real integration and rendered nothing. Its test passed only because the fixture omitted the field the guard tested. Changelog: skip Signed-off-by: Luka Breitig <[email protected]>
Luka Breitig(02 Sept 26)
fix(core): make the Exchange sync survive a real Exchange server Three faults that a live Exchange Server would have hit on the first sync, none of which the grommunio test server exercises. The free/busy read asked for the whole 730-day sync window in one GetUserAvailability call. Exchange caps that window at 42 days by default, so the request would have been refused outright. Because the busy read runs first and its failure stops the run, nothing would have been written at all: the mailbox would have shown as connected while the diary read as completely free. The window is now sliced into consecutive requests within the cap, concatenated in order, and the first failing slice still fails the whole read so a partial busy set can never be published. The item read keeps its full window; CalendarView has no such limit. The provider was registered as having a circuit breaker but no EWS call ever went through one, so an unreachable server was retried on every cycle for every integration and its health always reported as good. Every request now passes through the breaker, keyed per host as the CalDAV family does. Only transport-level faults trip it: a rejected credential, a missing endpoint or a SOAP fault is one integration's configuration problem and must not refuse another user's server. The worker's moduledoc claimed that a failed item read still stamped the sync and invalidated the availability cache. It does not, and should not; the doc now says what the code does. Changelog: skip Signed-off-by: Luka Breitig <[email protected]>
Luka Breitig(02 Sept 26)
fix(core): trip the calendar circuit breaker on repeated server errors The breaker scores a raw `{:http_error, 500}` as a failure, but the calendar clients classify a 5xx into the atom `:server_error` before the outcome is scored, and that atom was missing from the transport-reason list. A calendar server answering nothing but 500s was therefore scored as "ignore" every time and its breaker never opened, so the server kept being called on every sync cycle and its health always read as good. Signed-off-by: Luka Breitig <[email protected]>
Luka Breitig(02 Sept 26)
test(core): make the branding and read-only picker tests order-independent Two tests failed under the full suite while passing on their own. The read-only calendar picker test sent the parent LiveView a refresh message and rendered once. The handler answers with send_update/3, which queues a second message to the LiveView instead of updating the component inline, so the one render only synced past the refresh message and could race the component update. It now renders twice; the second render is ordered after the update. The branding test asserted that a rejected upload left no branding directory behind, but the admin branding LiveView test removes its uploaded file and leaves the directory, so the assertion failed when that module ran first. The directory is now cleared before each test as well as after it. Signed-off-by: Luka Breitig <[email protected]>
Luka Breitig(01 Sept 26)
Merge branch 'main' into exchange
Luka Breitig(01 Sept 26)
refactor(core): name the Exchange seeder's date helper for what it returns `to_date/1` said how it worked; `series_start_date/1` says what the caller wants, which is the day an EWS recurrence range starts on. It also stops the `ThinWrapperFunctions` check flagging the clause that forwards to `DateTime.to_date/1`, since that check matches on the names agreeing. Signed-off-by: Luka Breitig <[email protected]>
Luka Breitig(01 Sept 26)
test(core): stop the Exchange diagnostics tests colliding with the Diagnostics suite `calendar_test.exs` already defines `Calendar.DiagnosticsTest`, and the new Exchange tests took the same module name. Two files defining one module fail outright when both are loaded together, which is what `mix test.affected` does for any change touching `diagnostics.ex`; the full suite happened to load them in an order that did not trip it, so it passed. Renamed to `DiagnosticsExchangeTest`, in a file named to match. They stay separate from the suite they collided with rather than being merged into it, because the two need different case templates: that one is a `DataCase` stubbing the HTTP client, while these assert on the SOAP actually put on the wire and so need `ExchangeCase`'s real transport. Signed-off-by: Luka Breitig <[email protected]>
Luka Breitig(01 Sept 26)
refactor(core): extract one XML escaper for the CalDAV and EWS builders `SyncCollectionReport.xml_escape/1` and `Requests.escape/1` replaced the same five metacharacters in the same order for the same purpose, and both feed request bodies sent to third-party servers. A divergence between them would have surfaced as a malformed request against one protocol only, which is a slow thing to diagnose. Both now call `Calendar.Utils.XmlEscape`. iCalendar escaping stays where it is: that grammar uses backslashes rather than entity references, and sharing a module with it would invite calling the wrong one. The round-trip test is the one that earns its place. Escaping `&` last would double-escape every entity the other four replacements introduce, and each character would still look escaped when checked one at a time; only parsing the document back and comparing byte-for-byte catches it. The `is_binary` guard is likewise pinned rather than assumed, so a `nil` raises instead of being rendered as an empty string and sent as a request that means something else. Signed-off-by: Luka Breitig <[email protected]>
Luka Breitig(01 Sept 26)
fix(core): send mail through self-hosted SMTP relays Port 465 could not deliver to any SMTP server. gen_smtp reads :tls_options only when upgrading a connection with STARTTLS; on the implicit-TLS path it builds the socket options from :sockopts and ignores :tls_options entirely, so the handshake ran with OTP's default verify_peer and no CA certificates and every send failed with {:options, :incompatible, [verify: :verify_peer, cacerts: :undefined]}. The TLS options are now repeated as :sockopts for that path. This is how a self-hosted relay is usually reached: several mail servers open 465 for submission and never listen on 587 at all. Certificate verification also had no escape hatch, which a relay you host yourself normally needs, since it serves a self-signed certificate until pointed at Let's Encrypt. Two variables cover it: SMTP_CACERTFILE trusts the CA that issued the certificate and keeps the connection authenticated, and SMTP_TLS_VERIFY=none accepts any certificate as a last resort, logging a warning that names what it gives up. With verification off no trust store is loaded or required. The startup probe now mirrors both settings rather than hard-coding verify_peer, so it can no longer report a relay healthy that cannot deliver a single email, which is how the port-465 failure stayed hidden. Signed-off-by: Luka Breitig <[email protected]>
Luka Breitig(01 Sept 26)
chore(core): let the calendar audit seed and read an Exchange mailbox The audit plants a fixture, reads it back and checks it normalises. Exchange is the one provider it cannot do that to: the provider refuses every write, so there is no write path to exercise and nothing on the server to read. `Exchange.Seeding` plants and hard-deletes fixtures over EWS. It is not a `Provider` callback, is not reachable from one, and is called from exactly one place: `Diagnostics`, on behalf of the audit. The provider stays read-only and its folders stay flagged read-only; none of that is softened. Seeding over EWS rather than over the CalDAV interface the development container also happens to expose: a real Exchange Server offers no such side door, and a fixture planted over CalDAV would audit the server's protocol conversion rather than the EWS representation the provider actually reads. The read side needed its own exception. This provider's `list_events/2` is a cache read, so the usual probe would report an empty mailbox as a pass on any server, and an ephemeral audit target has no cache at all. `fetch_and_normalise_provider_events/3` now dispatches it to the live item read, and `fetch_exchange_busy_intervals/3` exposes the second read, which is the only one that expands a recurring series. `Requests.escape/1` becomes public so the two EWS body builders share one escaper instead of growing a copy each. Signed-off-by: Luka Breitig <[email protected]>
Luka Breitig(01 Sept 26)
feat(core): connect a Microsoft Exchange calendar from the dashboard Adds the connection form that makes the Exchange (EWS) provider reachable, and lifts the config pin that kept it off the provider list. An on-premises Exchange 2016/2019/SE mailbox can now be connected, its calendar folders discovered and selected, and its busy time blocks the owner's availability. The form is its own two-step component rather than the shared CalDAV one. An EWS integration needs two fields no CalDAV form has: the mailbox address, which is what the availability read is addressed to and which a domain login cannot stand in for, and a TLS verification toggle for the self-signed certificates on-premises deployments routinely carry. Its folder selection is keyed on the opaque `FolderId` the server issues rather than on a path. The mailbox address is deliberately not validated against the public TLD list. On-premises mailboxes routinely sit in `.local`, `.corp` or `.internal` domains, so the field is checked for the shape of an address and nothing more; validating it strictly would refuse exactly the deployments this provider exists for. Every discovered folder is persisted read-only. The provider refuses writes, and folder discovery deliberately does not set the flag itself because `FindFolder` reports no rights; this is the point where the write path is offered, so it is where the property belongs. Without it an Exchange folder would be eligible as a default booking calendar and every booking written to it would fail. Creation deliberately omits the first-integration primary promotion, for the reason the subscription path omits it too: that helper promotes unconditionally, bypassing the read-only guard, and would leave a user whose only calendar is Exchange with a primary that fails every booking write. Signed-off-by: Luka Breitig <[email protected]>
Luka Breitig(01 Sept 26)
fix(core): tell a refused Exchange certificate apart from a network failure A rejected TLS handshake fell through to the generic transport clause and was reported as `:network_error`, which asks the account owner to check their network connection and server URL. Neither is wrong, and the actual fix sits in a checkbox on the same form. An on-premises Exchange behind a self-signed or internal-CA certificate is the ordinary case for this provider, so the handshake failure gets its own reason and its own sentence. Both `Mint.TransportError` and `Req.TransportError` are matched: Req wraps Mint's, and which one surfaces depends on where in the stack the handshake failed. Changelog: skip Signed-off-by: Luka Breitig <[email protected]>
Luka Breitig(01 Sept 26)
refactor(core): name read-only providers and guard primary selection on it `PrimarySelection` guarded promotion on `subscription?/1`, which named the reason a subscribed feed cannot be written to rather than the property that matters: the provider's module refuses every write. `read_only?/1` states that property directly. Behaviour is unchanged today. `:ics_url` is the only subscription provider and it is read-only, so the two predicates answer identically for every provider that currently ships. The list is its own rather than derived from a family, because being read-only cuts across two of them and one of them may stop being read-only: a writable EWS phase would drop `:exchange` from the list without touching the `:ews` family. Signed-off-by: Luka Breitig <[email protected]>
Luka Breitig(01 Sept 26)
chore(core): let the visitor clock formatter accept a bare time `format_time_by_locale/1` was spec'd as taking a `DateTime`, but it only ever reads the clock fields and hands them to `LocaleFormat.format_time/2`, which declares `Calendar.time()`. The hour labels on the two-tier picker and the interval examples in the meeting type form both pass a `Time`, which is correct at runtime and which Dialyzer rightly rejected against the narrower contract. Widen the spec to `Calendar.time()`, which is what the body has always supported and what every caller already satisfies. Signed-off-by: Luka Breitig <[email protected]>
Luka Breitig(01 Sept 26)
Merge branch 'main' into slot-intervals
Luka Breitig(01 Sept 26)
feat(core): let hosts type any booking interval, not just the listed ones The interval control offered a fixed list, so an organiser wanting times every seven minutes had no way to ask for it: a value already stored outside the list was preserved and shown, but could never be created. The list now ends in a "Custom…" entry that reveals a number input bounded by the same range the changeset validates, and a stored off-list value opens the form in that mode. Both controls post the same parameter, so they cannot disagree about the value, and the sentinel that selects custom mode is dropped before validation ever sees it. Underneath the control, a line of grey help text spells the choice out in times: "Times will be offered every 7 minutes: 09:00, 09:07, 09:14…", formatted for the organiser's locale. It rewrites itself as the value changes, so the effect of an interval is visible before saving rather than only on the public booking page. The preset list moves into the shared custom-input table alongside the other schedule policy fields, which is where the tag list, the default mode map and the preset check all derive from. Changelog: skip Signed-off-by: Luka Breitig <[email protected]>
Luka Breitig(01 Sept 26)
fix(core): tell an opened booking hour apart from the selected one In Rhythm the open hour and the selected hour rendered identically, so once a time was picked there was no way to see which hour the minute list below belonged to. Quill marked the open hour, but with a rule down the left edge of the whole minute row, which sits at the first hour in the row rather than the open one and therefore pointed at the wrong button whenever the open hour was not first. Both themes now outline the open hour and leave the filled treatment to the selection, and the minutes sit in their own bordered drawer instead of leaning on an edge rule. Changelog: skip Signed-off-by: Luka Breitig <[email protected]>
Luka Breitig(01 Sept 26)
fix(core): keep the hour picker off grids it cannot shorten Grouping by hour costs a click, and it only earns that click when an hour actually holds several slots. It was applied whenever the interval was shorter than the meeting length, which took in 30- and 60-minute intervals: at 30 an hour holds two slots, the density a duration-locked 30-minute meeting already shows flat, and at 60 it holds one, so opening an hour revealed a single time repeating the button just clicked. A 90-minute meeting on a 60-minute interval rendered eight accordion buttons hiding one time each. Bound the grouping to intervals of 20 minutes or less, the coarsest interval still putting three starts in an hour. A fixed interval bound rather than a slot count, because the count varies with how booked up a day is, and the same page would then render differently from one day to the next. Changelog: skip Signed-off-by: Luka Breitig <[email protected]>
Luka Breitig(01 Sept 26)
fix(core): keep a switched-off setting's own toggle legible A boolean setting that was switched off dimmed its whole row, including the Enabled/Disabled control itself. That made the one control able to turn the setting back on read as though it were unclickable, which was most obvious on Admin alerts. The row dimmed on a single flag that conflated two different states. They are now separate: a dependent control whose parent is off is genuinely unusable and still renders disabled, while a setting that is merely switched off dims only its description and leaves its control at full contrast. Both settings toggles also emit a real aria-pressed="true"/"false". Passing the boolean straight through rendered an empty attribute for true and omitted it for false, so the control never announced its state. Signed-off-by: Luka Breitig <[email protected]>
Luka Breitig(01 Sept 26)
feat(core): let admins set a fallback language per surface Adds two admin settings, one for the dashboard and one for public booking pages, choosing the language each falls back to when a visitor cannot be matched to a supported one. They are fallbacks, not overrides: a saved user preference, a locale-prefixed path, an explicit ?locale= and a supported Accept-Language all still win. The setting only replaces the final link in the resolution chain, which previously went straight to the instance-wide default. A Spanish or Dutch browser on a German host's booking page now gets German instead of English. The booking fallback also lands on the attendee_locale stored with a booking, so it governs the confirmation, reminder and cancellation mail sent long after the visit. The two surfaces stay independent: the same booking mails the attendee in the booking language and the organiser in the dashboard one. This also closes the hardcoded "en" that ad-hoc bookings previously stored. Both settings are validated against the configured locale set on write and again on read, so a language later removed from the build degrades to the instance default rather than rendering untranslated text. Signed-off-by: Luka Breitig <[email protected]>
Luka Breitig(01 Sept 26)
feat(core): group admin settings into tabs The admin settings page had grown to seven sections on one scroll. It is now three tabs, Authentication, Email and General, alongside the existing Users tab, each with its own URL. Which sections a tab shows is declared once, in TymeslotWeb.AdminLive.Tabs, and that declaration drives the tab bar, the sections rendered, and the live_actions the router uses. Adding a section means editing one list, and a section belonging to no tab is unreachable rather than silently rendered twice. /admin lands on Authentication, and /admin/settings still resolves there so existing bookmarks keep working. The bar wraps rather than scrolling sideways, so tabs stay visible as the list grows. Signed-off-by: Luka Breitig <[email protected]>
Luka Breitig(01 Sept 26)
Merge branch 'main' into exchange
Luka Breitig(01 Sept 26)
Merge branch 'main' into slot-intervals
Luka Breitig(01 Sept 26)
test(core): make the login timing-attack test falsifiable under load config/test.exs pins bcrypt to 4 rounds to keep the suite quick, which left the dummy hash the same order of magnitude as scheduler noise on a contended runner. Measured that way the test was not merely flaky, it was unfalsifiable: sampled under deliberate CPU starvation, an intact build read as low as 0.67 while a build with the dummy hash deleted read as high as 0.71, on either side of the 0.5 threshold it asserts. Raise the cost factor to 10 for the duration of the test so the hash dominates the measurement, and alternate the two samples so a burst of load moves both halves of a pair together rather than skewing one block alone. Under the same starvation this now reads 0.97-1.00 intact and 0.009-0.011 with the dummy hash removed. The cost factor is global to the VM, so the test moves out to its own async: false module. Signed-off-by: Luka Breitig <[email protected]>
Tymeslot Website
Website
Open-Source Meeting Scheduling Software · Tymeslot
Eliminate scheduling back-and-forth with Tymeslot. Share your availability, let others book instantly, and sync with your calendar and video tools.
Redirects
Does not redirect
Security Checks
2 security checks failed (63 passed)
- Domain Recently Created
- Domain Very Recently Created
Server Details
- IP Address94.16.119.60
- Hostnamev2202503260852324142.megasrv.de
- LocationNuremberg,Bayern,Germany,EU
- ISPAnexia Cloud Solutions GmbH
- ASNAS197540
Associated Countries
US
DE
Safety Score
Website marked as safe
100%
Blacklist Check
tymeslot.app was found on 0 blacklists
- AntiSocial Blacklist
- Artists Against 419
- Badbitcoin
- Bambenek Consulting
- CERT Polska
- CoinBlockerLists
- CRDF
- CryptoScamDB
- EtherAddressLookup
- EtherScamDB
- Fake Website Buster
- MetaMask EthPhishing
- NABP Not Recommended Sites
- OpenPhish
- PetScams
- PhishFeed
- PhishFort
- Phishing.Database
- PhishStats
- PhishTank
- Phishunt
- RPiList Not Serious
- Scam.Directory
- SecureReload Phishing List
- Spam404
- StopGunScams
- Suspicious Hosting IP
- ThreatFox
- ThreatLog
- TweetFeed
- URLhaus
- ViriBack C2 Tracker
Website Preview
Tymeslot Reviews
More Scheduling
Open-source & self-hostable scheduling tool that helps you find the best date and time to meet. Create a poll with a few options, share the link, and let your participants vote on when they're available. No more back-and-forth emails.
About the Data: Tymeslot
Change History
- Added #681
Edit Tymeslot Data
You can edit Tymeslot's entry in this section of awesome-privacy.yml by submitting a PR to our GitHub repo.
Note that some of the information shown above has been aggregated from external
sources, a list of these can be found data documentation.
Origin Data
Modify Data
API
You can access Tymeslot's data programmatically via our API. Simply make a GET request to:
https://api.awesome-privacy.xyz/v1/services/tymeslotThe REST API is free, no-auth and CORS-enabled. To learn more, view the API Docs or read the API Usage Guide.
Share Tymeslot
Help your friends compare Scheduling, and pick privacy-respecting software and services.
Share Tymeslot and Awesome Privacy with your network!