pylon-api 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/README.md +17 -7
- data/lib/pylon/client.rb +107 -4
- data/lib/pylon/models/email_suppression.rb +13 -0
- data/lib/pylon/version.rb +1 -1
- data/lib/pylon.rb +2 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ae2b3657a71f50dfe1fbe13af8222fd19fb8026356543cae2b9372bb12bee3fa
|
|
4
|
+
data.tar.gz: 5232f1d4a792a6d06bf847ce5170bd4272166b7c47700f50929f676319dd0b88
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1d87c185ffa7b4657a9daaa49e87aa1e03b17913c2c65601270f076165902b1689b7c1e02a0142bfb26a10c751507987547fc49658848104e965861d454ba5fc
|
|
7
|
+
data.tar.gz: 0fc12093a40563fbb376d6cd84dcf90dfdcd3e359702ab1f99cf750c1b458436a8ccd806d4d9089d42afaadeffeec1a36b25a3faaae290f4f32ee92145fc567e
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.2.0] - 2026-09-20
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `list_email_suppressions` for `GET /email-suppressions` with optional `email`, `cursor`, and `limit` query parameters. Pass `email` to check a single address; an empty collection means it is not suppressed.
|
|
7
|
+
- `delete_email_suppression` for `DELETE /email-suppressions/{id}`
|
|
8
|
+
- `Pylon::Models::EmailSuppression` model (`id`, `email`, `reason`, `created_at`, `bounce_details`)
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- Documentation only: README examples now use `body_html` when creating issues and note the 365-day window for `list_issues`. No existing method signatures, paths, or return types changed.
|
|
12
|
+
|
|
13
|
+
## [1.1.1] - 2025-04-18
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- Fixed URL-based file attachments by ensuring they use proper multipart form encoding
|
|
17
|
+
|
|
18
|
+
## [1.1.0] - 2025-04-17
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- Support for URL-based file attachments
|
|
22
|
+
- Enhanced attachment handling for various file types
|
|
23
|
+
- Added MIME type detection for file uploads
|
|
24
|
+
|
|
3
25
|
## [1.0.0] - 2024-03-21
|
|
4
26
|
|
|
5
27
|
### Changed
|
data/README.md
CHANGED
|
@@ -55,25 +55,35 @@ account, response = client.get_account('account_id')
|
|
|
55
55
|
#### Issues
|
|
56
56
|
|
|
57
57
|
```ruby
|
|
58
|
-
# List issues (requires time range, max
|
|
58
|
+
# List issues (requires time range, max 365 days)
|
|
59
59
|
start_time = Time.now.utc - 86400 # 24 hours ago
|
|
60
60
|
end_time = Time.now.utc
|
|
61
61
|
|
|
62
|
-
issues
|
|
62
|
+
issues = client.list_issues(
|
|
63
63
|
start_time: start_time.strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
64
64
|
end_time: end_time.strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
65
65
|
page: 1,
|
|
66
|
-
per_page: 20
|
|
67
|
-
status: 'open' # optional filter
|
|
66
|
+
per_page: 20
|
|
68
67
|
)
|
|
69
68
|
|
|
70
|
-
# Create an issue
|
|
71
|
-
issue
|
|
69
|
+
# Create an issue (title and body_html are required by the API)
|
|
70
|
+
issue = client.create_issue(
|
|
72
71
|
title: 'New Issue',
|
|
73
|
-
|
|
72
|
+
body_html: '<p>Issue description</p>',
|
|
73
|
+
requester_email: 'customer@example.com'
|
|
74
74
|
)
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
+
#### Email Suppressions
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
# Check whether a single address is suppressed (empty collection means not suppressed)
|
|
81
|
+
suppressions = client.list_email_suppressions(email: 'user@example.com')
|
|
82
|
+
|
|
83
|
+
# Remove a suppression
|
|
84
|
+
client.delete_email_suppression(suppressions.first.id) unless suppressions.size.zero?
|
|
85
|
+
```
|
|
86
|
+
|
|
77
87
|
#### Teams
|
|
78
88
|
|
|
79
89
|
```ruby
|
data/lib/pylon/client.rb
CHANGED
|
@@ -7,7 +7,22 @@ module Pylon
|
|
|
7
7
|
# client = Pylon::Client.new(api_key: 'your_api_key')
|
|
8
8
|
# issues = client.list_issues(start_time: '2024-03-01T00:00:00Z', end_time: '2024-03-31T23:59:59Z')
|
|
9
9
|
# issues.each { |issue| puts issue.title }
|
|
10
|
+
# rubocop:disable Metrics/ClassLength
|
|
10
11
|
class Client
|
|
12
|
+
# Common MIME types for file uploads
|
|
13
|
+
MIME_TYPES = {
|
|
14
|
+
".jpg" => "image/jpeg",
|
|
15
|
+
".jpeg" => "image/jpeg",
|
|
16
|
+
".png" => "image/png",
|
|
17
|
+
".gif" => "image/gif",
|
|
18
|
+
".pdf" => "application/pdf",
|
|
19
|
+
".doc" => "application/msword",
|
|
20
|
+
".docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
21
|
+
".xls" => "application/vnd.ms-excel",
|
|
22
|
+
".xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
23
|
+
".csv" => "text/csv",
|
|
24
|
+
".txt" => "text/plain"
|
|
25
|
+
}
|
|
11
26
|
# Base URL for the Pylon API
|
|
12
27
|
BASE_URL = "https://api.usepylon.com"
|
|
13
28
|
|
|
@@ -45,10 +60,36 @@ module Pylon
|
|
|
45
60
|
|
|
46
61
|
# Create a new attachment
|
|
47
62
|
#
|
|
48
|
-
# @param file [String] The file
|
|
63
|
+
# @param file [File, String, nil] The file to upload (File object or file content)
|
|
64
|
+
# @param description [String, nil] A text description of the file
|
|
65
|
+
# @param file_url [String, nil] A URL to fetch the file from if file is not provided
|
|
49
66
|
# @return [Models::Attachment] Created attachment object
|
|
50
|
-
|
|
51
|
-
|
|
67
|
+
# @raise [ArgumentError] If neither file nor file_url is provided
|
|
68
|
+
def create_attachment(file = nil, description: nil, file_url: nil)
|
|
69
|
+
if file.nil? && file_url.nil?
|
|
70
|
+
raise ArgumentError, "Either file or file_url must be provided"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
params = {}
|
|
74
|
+
params[:description] = description if description
|
|
75
|
+
params[:file_url] = file_url if file_url
|
|
76
|
+
|
|
77
|
+
if file
|
|
78
|
+
if file.is_a?(::File) || (file.respond_to?(:path) && file.respond_to?(:read))
|
|
79
|
+
# For File objects or IO-like objects
|
|
80
|
+
params[:file] = Faraday::UploadIO.new(file, mime_type_for_file(file), File.basename(file.path))
|
|
81
|
+
else
|
|
82
|
+
# For raw content as string, create a temp file
|
|
83
|
+
temp_file = Tempfile.new(["upload", ".bin"])
|
|
84
|
+
temp_file.binmode
|
|
85
|
+
temp_file.write(file)
|
|
86
|
+
temp_file.rewind
|
|
87
|
+
params[:file] = Faraday::UploadIO.new(temp_file, "application/octet-stream")
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Always use multipart for attachments, whether file or file_url
|
|
92
|
+
post_multipart("/attachments", params, model_class: Models::Attachment)
|
|
52
93
|
end
|
|
53
94
|
|
|
54
95
|
# Get details for a specific attachment
|
|
@@ -111,7 +152,30 @@ module Pylon
|
|
|
111
152
|
post("/custom_fields", body: params)
|
|
112
153
|
end
|
|
113
154
|
|
|
114
|
-
#
|
|
155
|
+
# List email suppressions, most recently suppressed first
|
|
156
|
+
#
|
|
157
|
+
# Pass +email+ to check a single address. An empty collection means the
|
|
158
|
+
# address is not suppressed. Uses the API's cursor-based pagination.
|
|
159
|
+
#
|
|
160
|
+
# @param email [String, nil] Only return the suppression for this address, if one exists
|
|
161
|
+
# @param cursor [String, nil] Cursor for the next page of results
|
|
162
|
+
# @param limit [Integer, nil] Number of suppressions to fetch (API default 100, max 1000)
|
|
163
|
+
# @return [Models::Collection<Models::EmailSuppression>] Collection of email suppression objects
|
|
164
|
+
def list_email_suppressions(email: nil, cursor: nil, limit: nil)
|
|
165
|
+
query = { email: email, cursor: cursor, limit: limit }.compact
|
|
166
|
+
get("/email-suppressions", query: query,
|
|
167
|
+
model_class: Models::EmailSuppression, collection: true)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Remove an email suppression so Pylon resumes sending to the address
|
|
171
|
+
#
|
|
172
|
+
# @param suppression_id [String] The ID of the email suppression to remove
|
|
173
|
+
# @return [Array(Hash, Faraday::Response)] Response data and raw response
|
|
174
|
+
def delete_email_suppression(suppression_id)
|
|
175
|
+
delete("/email-suppressions/#{suppression_id}")
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Lists issues within a specified time range (max 365 days)
|
|
115
179
|
#
|
|
116
180
|
# @param start_time [String] Start time in RFC3339 format
|
|
117
181
|
# @param end_time [String] End time in RFC3339 format
|
|
@@ -324,6 +388,17 @@ module Pylon
|
|
|
324
388
|
end
|
|
325
389
|
end
|
|
326
390
|
|
|
391
|
+
# Determine MIME type for file
|
|
392
|
+
#
|
|
393
|
+
# @param file [File] The file to determine MIME type for
|
|
394
|
+
# @return [String] The MIME type
|
|
395
|
+
def mime_type_for_file(file)
|
|
396
|
+
return "application/octet-stream" unless file.respond_to?(:path)
|
|
397
|
+
|
|
398
|
+
ext = File.extname(file.path).downcase
|
|
399
|
+
MIME_TYPES[ext] || "application/octet-stream"
|
|
400
|
+
end
|
|
401
|
+
|
|
327
402
|
# Handle API response and raise appropriate errors
|
|
328
403
|
#
|
|
329
404
|
# @param response [Faraday::Response] The API response
|
|
@@ -427,6 +502,34 @@ module Pylon
|
|
|
427
502
|
handle_response(connection.post(path, body.to_json), model_class, collection)
|
|
428
503
|
end
|
|
429
504
|
|
|
505
|
+
# Make a multipart POST request for file uploads
|
|
506
|
+
#
|
|
507
|
+
# @param path [String] The API endpoint path
|
|
508
|
+
# @param params [Hash] Request parameters including file uploads
|
|
509
|
+
# @param model_class [Class] The model class to use for wrapping the response
|
|
510
|
+
# @param collection [Boolean] Whether the response is a collection of items
|
|
511
|
+
# @return [Models::Base, Models::Collection, Array] Model, Collection, or response data array
|
|
512
|
+
def post_multipart(path, params = {}, model_class: nil, collection: false)
|
|
513
|
+
# Create a connection without the default JSON content type for multipart uploads
|
|
514
|
+
multipart_conn = Faraday.new(@base_url) do |f|
|
|
515
|
+
f.request :multipart
|
|
516
|
+
# Only force multipart for file_url uploads, omit url_encoded middleware
|
|
517
|
+
f.response :json, content_type: /\bjson$/
|
|
518
|
+
f.response :logger if @debug
|
|
519
|
+
f.adapter Faraday.default_adapter
|
|
520
|
+
f.headers["Authorization"] = "Bearer #{api_key}"
|
|
521
|
+
f.headers["Accept"] = "application/json"
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
# If we have a file_url but no actual file, create a dummy file part to force multipart encoding
|
|
525
|
+
if params[:file_url] && !params[:file]
|
|
526
|
+
# Create an empty file part to force multipart encoding
|
|
527
|
+
params[:_dummy] = Faraday::FilePart.new(StringIO.new(""), "application/octet-stream", "dummy")
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
handle_response(multipart_conn.post(path, params), model_class, collection)
|
|
531
|
+
end
|
|
532
|
+
|
|
430
533
|
# Make a PATCH request
|
|
431
534
|
#
|
|
432
535
|
# @param path [String] The API endpoint path
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pylon
|
|
4
|
+
module Models
|
|
5
|
+
# An email address Pylon will not send to, either because a message
|
|
6
|
+
# hard bounced or because someone suppressed it manually.
|
|
7
|
+
#
|
|
8
|
+
# Attributes: id, email, reason ("hard_bounce" or "manual"),
|
|
9
|
+
# created_at (RFC3339), bounce_details
|
|
10
|
+
class EmailSuppression < Base
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
data/lib/pylon/version.rb
CHANGED
data/lib/pylon.rb
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "faraday"
|
|
4
4
|
require "faraday/multipart"
|
|
5
5
|
require "json"
|
|
6
|
+
require "tempfile"
|
|
6
7
|
|
|
7
8
|
require_relative "pylon/version"
|
|
8
9
|
require_relative "pylon/models/base"
|
|
@@ -16,6 +17,7 @@ require_relative "pylon/models/attachment"
|
|
|
16
17
|
require_relative "pylon/models/contact"
|
|
17
18
|
require_relative "pylon/models/ticket_form"
|
|
18
19
|
require_relative "pylon/models/article"
|
|
20
|
+
require_relative "pylon/models/email_suppression"
|
|
19
21
|
require_relative "pylon/client"
|
|
20
22
|
|
|
21
23
|
module Pylon
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pylon-api
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ben Odom
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-09-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: faraday
|
|
@@ -140,6 +140,7 @@ files:
|
|
|
140
140
|
- lib/pylon/models/base.rb
|
|
141
141
|
- lib/pylon/models/collection.rb
|
|
142
142
|
- lib/pylon/models/contact.rb
|
|
143
|
+
- lib/pylon/models/email_suppression.rb
|
|
143
144
|
- lib/pylon/models/issue.rb
|
|
144
145
|
- lib/pylon/models/tag.rb
|
|
145
146
|
- lib/pylon/models/team.rb
|